From b5da12932476484f1c6342fe5adba62ce4f22fa3 Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 26 Jun 2024 23:19:18 +0300 Subject: [PATCH 01/51] Separate the header --- .../terminal/handler/ghtrigger/ghtrigger.go | 11 +- internal/terminal/handler/handler.go | 109 ++++------- internal/terminal/handler/header/header.go | 176 ++++++++++++++++++ internal/terminal/handler/header/keymap.go | 32 ++++ .../handler/information/information.go | 27 +-- internal/terminal/style/style.go | 14 ++ 6 files changed, 275 insertions(+), 94 deletions(-) create mode 100644 internal/terminal/handler/header/header.go create mode 100644 internal/terminal/handler/header/keymap.go diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index 2ff2a67..9b9f3e8 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/termkit/gama/internal/terminal/handler/header" "slices" "strings" "time" @@ -27,7 +28,6 @@ type ModelGithubTrigger struct { workflowContent *workflow.Pretty tableReady bool isTriggerable bool - currentTab *int forceUpdateWorkflowHistory *bool optionInit bool optionCursor int @@ -47,6 +47,8 @@ type ModelGithubTrigger struct { Keys keyMap // models + header *header.Header + Help help.Model Viewport *viewport.Model modelError hdlerror.ModelError @@ -81,7 +83,7 @@ func SetupModelGithubTrigger(githubUseCase gu.UseCase, selectedRepository *hdlty ti.CharLimit = 72 return &ModelGithubTrigger{ - currentTab: currentTab, + header: header.NewHeader(), forceUpdateWorkflowHistory: forceUpdateWorkflowHistory, Help: help.New(), Keys: keys, @@ -166,7 +168,7 @@ func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.textInput.Focus() } } - case "enter": + case "enter", tea.KeyEnter.String(): if m.triggerFocused && m.isTriggerable { go m.triggerWorkflow() } @@ -597,7 +599,8 @@ func (m *ModelGithubTrigger) triggerWorkflow() { time.Sleep(1 * time.Second) *m.forceUpdateWorkflowHistory = true // force update workflow history }() - *m.currentTab = 2 // switch tab to workflow history + + m.header.SetCurrentTab(2) // switch tab to workflow history } func (m *ModelGithubTrigger) emptySelector() string { diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index b2cd4a0..80012e6 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -2,6 +2,7 @@ package handler import ( "fmt" + "github.com/termkit/gama/internal/terminal/handler/header" "strings" "time" @@ -22,19 +23,15 @@ import ( ) type model struct { - // current handler's properties - TabsWithColor []string - currentTab *int - isTabActive bool - // Shared properties SelectedRepository *hdltypes.SelectedRepository - lockTabs *bool // lockTabs will be set true if test connection fails // models viewport viewport.Model timer timer.Model + modelHeader *header.Header + modelInfo tea.Model actualModelInfo *hdlinfo.ModelInfo @@ -62,27 +59,21 @@ const ( func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Model { var currentTab = new(int) var forceUpdateWorkflowHistory = new(bool) - var lockTabs = new(bool) - - *lockTabs = true // by default lock tabs - - tabsWithColor := []string{"Info", "Repository", "Workflow History", "Workflow", "Trigger"} selectedRepository := hdltypes.SelectedRepository{} // setup models - hdlModelInfo := hdlinfo.SetupModelInfo(githubUseCase, version, lockTabs) + hdlModelHeader := header.NewHeader() + hdlModelInfo := hdlinfo.SetupModelInfo(githubUseCase, version) hdlModelGithubRepository := hdlgithubrepo.SetupModelGithubRepository(githubUseCase, &selectedRepository) hdlModelWorkflowHistory := hdlworkflowhistory.SetupModelGithubWorkflowHistory(githubUseCase, &selectedRepository, forceUpdateWorkflowHistory) hdlModelWorkflow := hdlWorkflow.SetupModelGithubWorkflow(githubUseCase, &selectedRepository) hdlModelTrigger := hdltrigger.SetupModelGithubTrigger(githubUseCase, &selectedRepository, currentTab, forceUpdateWorkflowHistory) m := model{ - lockTabs: lockTabs, - currentTab: currentTab, - TabsWithColor: tabsWithColor, - timer: timer.NewWithInterval(1<<63-1, time.Millisecond*200), - modelInfo: hdlModelInfo, actualModelInfo: hdlModelInfo, + timer: timer.NewWithInterval(1<<63-1, time.Millisecond*200), + modelHeader: hdlModelHeader, + modelInfo: hdlModelInfo, actualModelInfo: hdlModelInfo, SelectedRepository: &selectedRepository, modelGithubRepository: hdlModelGithubRepository, actualModelGithubRepository: hdlModelGithubRepository, modelWorkflowHistory: hdlModelWorkflowHistory, directModelWorkflowHistory: hdlModelWorkflowHistory, @@ -91,6 +82,14 @@ func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Mod keys: keys, } + 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.Viewport = &m.viewport hdlModelInfo.Viewport = &m.viewport hdlModelGithubRepository.Viewport = &m.viewport hdlModelWorkflowHistory.Viewport = &m.viewport @@ -122,21 +121,13 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.viewport.Width = msg.Width m.viewport.Height = msg.Height case tea.KeyMsg: + m.modelHeader, cmd = m.modelHeader.Update(msg) + cmds = append(cmds, cmd) + cmds = append(cmds, m.handleTabContent(cmd, msg)) + switch { - case key.Matches(msg, m.keys.SwitchTabLeft): - if !*m.lockTabs { - *m.currentTab = max(*m.currentTab-1, 0) - } - cmds = append(cmds, m.handleTabContent(cmd, msg)) - case key.Matches(msg, m.keys.SwitchTabRight): - if !*m.lockTabs { - *m.currentTab = min(*m.currentTab+1, len(m.TabsWithColor)-1) - } - cmds = append(cmds, m.handleTabContent(cmd, msg)) case key.Matches(msg, m.keys.Quit): return m, tea.Quit - default: - cmds = append(cmds, m.handleTabContent(cmd, msg)) } case timer.TickMsg: m.timer, cmd = m.timer.Update(msg) @@ -156,24 +147,6 @@ func (m *model) View() string { var operationDoc string var helpDocHeight int - header := newHeader(&m.viewport) - for i, t := range m.TabsWithColor { - var tabStyle lipgloss.Style - isActive := i == *m.currentTab - if isActive { - tabStyle = ts.TitleStyleActive - } else { - if *m.lockTabs { - tabStyle = ts.TitleStyleDisabled - } else { - tabStyle = ts.TitleStyleInactive - } - } - header.addHeaderTab(t, tabStyle) - } - - mainDoc.WriteString("\n" + header.renderHeader() + "\n") - var width = lipgloss.Width(strings.Repeat("-", m.viewport.Width)) - 4 hdltypes.ScreenWidth = &width @@ -182,24 +155,34 @@ func (m *model) View() string { helpWindowStyle := ts.WindowStyleHelp.Width(width) operationWindowStyle := lipgloss.NewStyle() - switch *m.currentTab { + switch m.modelHeader.GetCurrentTab() { case 0: + mainDoc.WriteString("\n" + m.modelHeader.View() + "\n") + mainDoc.WriteString(dynamicWindowStyle.Render(m.modelInfo.View())) operationDoc = operationWindowStyle.Render(m.actualModelInfo.ViewStatus()) helpDoc = helpWindowStyle.Render(m.actualModelInfo.ViewHelp()) case 1: + mainDoc.WriteString("\n" + m.modelHeader.View() + "\n") + mainDoc.WriteString(dynamicWindowStyle.Render(m.modelGithubRepository.View())) operationDoc = operationWindowStyle.Render(m.actualModelGithubRepository.ViewStatus()) helpDoc = helpWindowStyle.Render(m.actualModelGithubRepository.ViewHelp()) case 2: + mainDoc.WriteString("\n" + m.modelHeader.View() + "\n") + mainDoc.WriteString(dynamicWindowStyle.Render(m.modelWorkflowHistory.View())) operationDoc = operationWindowStyle.Render(m.directModelWorkflowHistory.ViewStatus()) helpDoc = helpWindowStyle.Render(m.directModelWorkflowHistory.ViewHelp()) case 3: + mainDoc.WriteString("\n" + m.modelHeader.View() + "\n") + mainDoc.WriteString(dynamicWindowStyle.Render(m.modelWorkflow.View())) operationDoc = operationWindowStyle.Render(m.directModelWorkflow.ViewStatus()) helpDoc = helpWindowStyle.Render(m.directModelWorkflow.ViewHelp()) case 4: + mainDoc.WriteString("\n" + m.modelHeader.View() + "\n") + mainDoc.WriteString(dynamicWindowStyle.Render(m.modelTrigger.View())) operationDoc = operationWindowStyle.Render(m.actualModelTrigger.ViewStatus()) helpDoc = helpWindowStyle.Render(m.actualModelTrigger.ViewHelp()) @@ -219,7 +202,7 @@ func (m *model) View() string { } func (m *model) handleTabContent(cmd tea.Cmd, msg tea.Msg) tea.Cmd { - switch *m.currentTab { + switch m.modelHeader.GetCurrentTab() { case 0: m.modelInfo, cmd = m.modelInfo.Update(msg) case 1: @@ -233,31 +216,3 @@ func (m *model) handleTabContent(cmd tea.Cmd, msg tea.Msg) tea.Cmd { } return cmd } - -// --- Header --- - -// header is a helper for rendering the header of the terminal. -type header struct { - viewport *viewport.Model - titles []string -} - -// newHeader returns a new header. -func newHeader(viewport *viewport.Model) *header { - return &header{ - titles: make([]string, 0), - viewport: viewport, - } -} - -// addHeaderTab adds a tab to the header. -func (h *header) addHeaderTab(title string, style lipgloss.Style) { - h.titles = append(h.titles, style.Render(title)) -} - -// renderHeader renders the header. -func (h *header) renderHeader() string { - line := strings.Repeat("─", max(0, h.viewport.Width-79)) - - return lipgloss.JoinHorizontal(lipgloss.Center, append(h.titles, line)...) -} diff --git a/internal/terminal/handler/header/header.go b/internal/terminal/handler/header/header.go new file mode 100644 index 0000000..74d310a --- /dev/null +++ b/internal/terminal/handler/header/header.go @@ -0,0 +1,176 @@ +package header + +import ( + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "strings" + "sync" +) + +// Header is a helper for rendering the Header of the terminal. +type Header struct { + keys keyMap + + Viewport *viewport.Model + //timer timer.Model + + currentTab int + lockTabs bool + + commonHeaders []commonHeader + specialHeaders []specialHeader + + switchSpecialAnimation bool +} + +type commonHeader struct { + header string + rawHeader string + + inactiveStyle lipgloss.Style + activeStyle lipgloss.Style +} + +type specialHeader struct { + header string + rawHeader string + + firstStyle lipgloss.Style + secondStyle lipgloss.Style +} + +// Define sync.Once and NewHeader should return same instance +var ( + once sync.Once + h *Header +) + +// NewHeader returns a new Header. +func NewHeader() *Header { + once.Do(func() { + h = &Header{ + currentTab: 0, + lockTabs: true, + keys: keys, + } + }) + return h +} + +func (h *Header) SetCurrentTab(tab int) { + h.currentTab = tab +} + +func (h *Header) GetCurrentTab() int { + return h.currentTab +} + +func (h *Header) SetLockTabs(lock bool) { + h.lockTabs = lock +} + +func (h *Header) GetLockTabs() bool { + return h.lockTabs +} + +func (h *Header) AddCommonHeader(header string, inactiveStyle, activeStyle lipgloss.Style) { + h.commonHeaders = append(h.commonHeaders, commonHeader{ + header: header, + rawHeader: header, + inactiveStyle: inactiveStyle, + activeStyle: activeStyle, + }) +} + +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) Init() tea.Cmd { + //return h.timer.Init() + return nil +} + +func (h *Header) Update(msg tea.Msg) (*Header, tea.Cmd) { + //var cmds []tea.Cmd + + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, h.keys.SwitchTabLeft): + if !h.lockTabs { + h.currentTab = max(h.currentTab-1, 0) + } + case key.Matches(msg, h.keys.SwitchTabRight): + if !h.lockTabs { + h.currentTab = min(h.currentTab+1, len(h.commonHeaders)-1) + } + } + + //case timer.TickMsg: + //h.switchSpecialAnimation = !h.switchSpecialAnimation + + //var cmd tea.Cmd + //h.timer, cmd = h.timer.Update(msg) + //return h, cmd + // + //case timer.StartStopMsg: + // var cmd tea.Cmd + //h.timer, cmd = h.timer.Update(msg) + //return h, cmd + + //case tea.KeyMsg: + // switch { + // case key.Matches(msg, m.keymap.quit): + // m.quitting = true + // return m, tea.Quit + // case key.Matches(msg, m.keymap.reset): + // m.timer.Timeout = timeout + // case key.Matches(msg, m.keymap.start, m.keymap.stop): + // return m, m.timer.Toggle() + // } + } + + return h, nil +} + +// View renders the Header. +func (h *Header) View() string { + var titles string + titles += "BBEEE" + for _, title := range h.commonHeaders { + titles += title.rawHeader + titles += "LLL RRR" + } + titleLen := len(titles) + specialTitleLen := len(h.specialHeaders[0].header) + + var specialHeader string + specialHeader = h.specialHeaders[0].header + + renderedTitles := make([]string, 0, len(h.commonHeaders)) + for i, title := range h.commonHeaders { + if i == h.currentTab { + renderedTitles = append(renderedTitles, title.activeStyle.Render(title.header)) + } else { + renderedTitles = append(renderedTitles, title.inactiveStyle.Render(title.header)) + } + } + + if h.currentTab == -1 { + specialHeader = h.specialHeaders[0].firstStyle.Render(h.specialHeaders[0].header) + } else { + specialHeader = h.specialHeaders[0].secondStyle.Render(h.specialHeaders[0].header) + } + + line := strings.Repeat("─", h.Viewport.Width-(titleLen+specialTitleLen)) + + return lipgloss.JoinHorizontal(lipgloss.Center, append(renderedTitles, line, specialHeader)...) +} diff --git a/internal/terminal/handler/header/keymap.go b/internal/terminal/handler/header/keymap.go new file mode 100644 index 0000000..5e1feba --- /dev/null +++ b/internal/terminal/handler/header/keymap.go @@ -0,0 +1,32 @@ +package header + +import ( + "fmt" + "github.com/termkit/gama/internal/config" + + teakey "github.com/charmbracelet/bubbles/key" +) + +type keyMap struct { + SwitchTabRight teakey.Binding + SwitchTabLeft teakey.Binding + Quit teakey.Binding +} + +var keys = func() keyMap { + cfg, err := config.LoadConfig() + if err != nil { + panic(fmt.Sprintf("failed to load config: %v", err)) + } + return keyMap{ + SwitchTabRight: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.SwitchTabRight), + ), + SwitchTabLeft: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.SwitchTabLeft), + ), + Quit: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.Quit), + ), + } +}() diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index f06515d..afb6510 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -3,6 +3,7 @@ package information import ( "context" "fmt" + "github.com/termkit/gama/internal/terminal/handler/header" "strings" "time" @@ -23,10 +24,9 @@ type ModelInfo struct { // use cases github gu.UseCase - // lockTabs will be set true if test connection fails - lockTabs *bool - // models + modelHeader *header.Header + Help help.Model Viewport *viewport.Model modelError hdlerror.ModelError @@ -54,21 +54,22 @@ var ( applicationDescription string ) -func SetupModelInfo(githubUseCase gu.UseCase, version pkgversion.Version, lockTabs *bool) *ModelInfo { +func SetupModelInfo(githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { modelError := hdlerror.SetupModelError() + hdlModelHeader := header.NewHeader() s := spinner.New() s.Spinner = spinner.Pulse s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("120")) return &ModelInfo{ - github: githubUseCase, - version: version, - Help: help.New(), - Keys: keys, - modelError: modelError, - lockTabs: lockTabs, - spinner: s, + modelHeader: hdlModelHeader, + github: githubUseCase, + version: version, + Help: help.New(), + Keys: keys, + modelError: modelError, + spinner: s, } } @@ -153,13 +154,13 @@ func (m *ModelInfo) testConnection(ctx context.Context) { if err != nil { m.modelError.SetError(err) m.modelError.SetErrorMessage("failed to test connection, please check your token&permission") - *m.lockTabs = true + m.modelHeader.SetLockTabs(true) return } m.modelError.Reset() m.modelError.SetSuccessMessage("Welcome to GAMA!") - *m.lockTabs = false + m.modelHeader.SetLockTabs(false) go m.Update(m) } diff --git a/internal/terminal/style/style.go b/internal/terminal/style/style.go index 254d5b5..c07089d 100644 --- a/internal/terminal/style/style.go +++ b/internal/terminal/style/style.go @@ -44,4 +44,18 @@ var ( b.Left = "┤" return lipgloss.NewStyle().BorderStyle(b).Padding(0, 2).BorderForeground(lipgloss.Color("240")).Foreground(lipgloss.Color("240")) }() + + TitleStyleLiveModeOn = func() lipgloss.Style { + b := lipgloss.DoubleBorder() + b.Right = "├" + b.Left = "┤" + return lipgloss.NewStyle().BorderStyle(b).Padding(0, 2).BorderForeground(lipgloss.Color("#aa1010")).Foreground(lipgloss.Color("#ff0000")) + }() + + TitleStyleLiveModeOff = func() lipgloss.Style { + b := lipgloss.DoubleBorder() + b.Right = "├" + b.Left = "┤" + return lipgloss.NewStyle().BorderStyle(b).Padding(0, 2).BorderForeground(lipgloss.Color("#101010")) + }() ) From 5b4f1dc22ca2a73c6755828f4b236bd4fc873ade Mon Sep 17 00:00:00 2001 From: Engin Date: Thu, 27 Jun 2024 00:16:30 +0300 Subject: [PATCH 02/51] Make the code better --- .../handler/ghrepository/ghrepository.go | 5 +- .../terminal/handler/ghtrigger/ghtrigger.go | 7 +- .../terminal/handler/ghworkflow/ghworkflow.go | 5 +- .../ghworkflowhistory/ghworkflowhistory.go | 5 +- internal/terminal/handler/handler.go | 121 ++++++------------ internal/terminal/handler/header/header.go | 5 +- .../handler/information/information.go | 9 +- 7 files changed, 63 insertions(+), 94 deletions(-) diff --git a/internal/terminal/handler/ghrepository/ghrepository.go b/internal/terminal/handler/ghrepository/ghrepository.go index 75892ea..30204b1 100644 --- a/internal/terminal/handler/ghrepository/ghrepository.go +++ b/internal/terminal/handler/ghrepository/ghrepository.go @@ -54,7 +54,7 @@ var baseStyle = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("240")) -func SetupModelGithubRepository(githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository) *ModelGithubRepository { +func SetupModelGithubRepository(viewport *viewport.Model, githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository) *ModelGithubRepository { var tableRowsGithubRepository []table.Row tableGithubRepository := table.New( @@ -114,6 +114,7 @@ func SetupModelGithubRepository(githubUseCase gu.UseCase, selectedRepository *hd tabOptions := taboptions.NewOptions(&modelError) return &ModelGithubRepository{ + Viewport: viewport, Help: help.New(), Keys: keys, github: githubUseCase, @@ -215,7 +216,7 @@ func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { m.actualModelTabOptions.SetStatus(taboptions.OptionIdle) } -func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *ModelGithubRepository) Update(msg tea.Msg) (*ModelGithubRepository, tea.Cmd) { var cmds []tea.Cmd var cmd tea.Cmd diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index 9b9f3e8..c865d6f 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -56,7 +56,7 @@ type ModelGithubTrigger struct { tableTrigger table.Model } -func SetupModelGithubTrigger(githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository, currentTab *int, forceUpdateWorkflowHistory *bool) *ModelGithubTrigger { +func SetupModelGithubTrigger(viewport *viewport.Model, githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository, forceUpdateWorkflowHistory *bool) *ModelGithubTrigger { var tableRowsTrigger []table.Row tableTrigger := table.New( @@ -83,7 +83,8 @@ func SetupModelGithubTrigger(githubUseCase gu.UseCase, selectedRepository *hdlty ti.CharLimit = 72 return &ModelGithubTrigger{ - header: header.NewHeader(), + Viewport: viewport, + header: header.NewHeader(viewport), forceUpdateWorkflowHistory: forceUpdateWorkflowHistory, Help: help.New(), Keys: keys, @@ -102,7 +103,7 @@ func (m *ModelGithubTrigger) Init() tea.Cmd { return textinput.Blink } -func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *ModelGithubTrigger) Update(msg tea.Msg) (*ModelGithubTrigger, tea.Cmd) { if m.SelectedRepository.WorkflowName == "" { m.modelError.Reset() m.modelError.SetDefaultMessage("No workflow selected.") diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index c79cf12..00a38e9 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -55,7 +55,7 @@ var baseStyle = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("240")) -func SetupModelGithubWorkflow(githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository) *ModelGithubWorkflow { +func SetupModelGithubWorkflow(viewport *viewport.Model, githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository) *ModelGithubWorkflow { var tableRowsTriggerableWorkflow []table.Row tableTriggerableWorkflow := table.New( @@ -81,6 +81,7 @@ func SetupModelGithubWorkflow(githubUseCase gu.UseCase, selectedRepository *hdlt tabOptions := taboptions.NewOptions(&modelError) return &ModelGithubWorkflow{ + Viewport: viewport, Help: help.New(), Keys: keys, github: githubUseCase, @@ -98,7 +99,7 @@ func (m *ModelGithubWorkflow) Init() tea.Cmd { return nil } -func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *ModelGithubWorkflow) Update(msg tea.Msg) (*ModelGithubWorkflow, tea.Cmd) { var cmd tea.Cmd if m.lastRepository != m.SelectedRepository.RepositoryName { diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go index 1fa798a..dad05bb 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go @@ -54,7 +54,7 @@ var baseStyle = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("240")) -func SetupModelGithubWorkflowHistory(githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository, forceUpdate *bool) *ModelGithubWorkflowHistory { +func SetupModelGithubWorkflowHistory(viewport *viewport.Model, githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository, forceUpdate *bool) *ModelGithubWorkflowHistory { var tableRowsWorkflowHistory []table.Row tableWorkflowHistory := table.New( @@ -80,6 +80,7 @@ func SetupModelGithubWorkflowHistory(githubUseCase gu.UseCase, selectedRepositor tabOptions := taboptions.NewOptions(&modelError) return &ModelGithubWorkflowHistory{ + Viewport: viewport, Help: help.New(), Keys: keys, github: githubUseCase, @@ -178,7 +179,7 @@ func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { return tea.Batch(m.modelTabOptions.Init()) } -func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (*ModelGithubWorkflowHistory, tea.Cmd) { if m.lastRepository != m.SelectedRepository.RepositoryName { m.tableReady = false m.cancelSyncWorkflowHistory() // cancel previous sync diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index 80012e6..fec5745 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -27,25 +27,15 @@ type model struct { SelectedRepository *hdltypes.SelectedRepository // models - viewport viewport.Model + viewport *viewport.Model timer timer.Model - modelHeader *header.Header - - modelInfo tea.Model - actualModelInfo *hdlinfo.ModelInfo - - modelGithubRepository tea.Model - actualModelGithubRepository *hdlgithubrepo.ModelGithubRepository - - modelWorkflow tea.Model - directModelWorkflow *hdlWorkflow.ModelGithubWorkflow - - modelWorkflowHistory tea.Model - directModelWorkflowHistory *hdlworkflowhistory.ModelGithubWorkflowHistory - - modelTrigger tea.Model - actualModelTrigger *hdltrigger.ModelGithubTrigger + modelHeader *header.Header + modelInfo *hdlinfo.ModelInfo + modelGithubRepository *hdlgithubrepo.ModelGithubRepository + modelWorkflow *hdlWorkflow.ModelGithubWorkflow + modelWorkflowHistory *hdlworkflowhistory.ModelGithubWorkflowHistory + modelTrigger *hdltrigger.ModelGithubTrigger // keymap keys keyMap @@ -57,30 +47,17 @@ const ( ) func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Model { - var currentTab = new(int) - var forceUpdateWorkflowHistory = new(bool) - - selectedRepository := hdltypes.SelectedRepository{} + forceUpdateWorkflowHistory := new(bool) + vp := &viewport.Model{Width: minTerminalWidth, Height: minTerminalHeight} + selectedRepository := &hdltypes.SelectedRepository{} // setup models - hdlModelHeader := header.NewHeader() - hdlModelInfo := hdlinfo.SetupModelInfo(githubUseCase, version) - hdlModelGithubRepository := hdlgithubrepo.SetupModelGithubRepository(githubUseCase, &selectedRepository) - hdlModelWorkflowHistory := hdlworkflowhistory.SetupModelGithubWorkflowHistory(githubUseCase, &selectedRepository, forceUpdateWorkflowHistory) - hdlModelWorkflow := hdlWorkflow.SetupModelGithubWorkflow(githubUseCase, &selectedRepository) - hdlModelTrigger := hdltrigger.SetupModelGithubTrigger(githubUseCase, &selectedRepository, currentTab, forceUpdateWorkflowHistory) - - m := model{ - timer: timer.NewWithInterval(1<<63-1, time.Millisecond*200), - modelHeader: hdlModelHeader, - modelInfo: hdlModelInfo, actualModelInfo: hdlModelInfo, - SelectedRepository: &selectedRepository, - modelGithubRepository: hdlModelGithubRepository, actualModelGithubRepository: hdlModelGithubRepository, - modelWorkflowHistory: hdlModelWorkflowHistory, directModelWorkflowHistory: hdlModelWorkflowHistory, - modelWorkflow: hdlModelWorkflow, directModelWorkflow: hdlModelWorkflow, - modelTrigger: hdlModelTrigger, actualModelTrigger: hdlModelTrigger, - keys: keys, - } + 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) hdlModelHeader.AddCommonHeader("Info", ts.TitleStyleInactive, ts.TitleStyleActive) hdlModelHeader.AddCommonHeader("Repository", ts.TitleStyleInactive, ts.TitleStyleActive) @@ -89,18 +66,23 @@ func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Mod hdlModelHeader.AddCommonHeader("Trigger", ts.TitleStyleInactive, ts.TitleStyleActive) hdlModelHeader.SetSpecialHeader("GAMA", ts.TitleStyleLiveModeOn, ts.TitleStyleLiveModeOff) - hdlModelHeader.Viewport = &m.viewport - hdlModelInfo.Viewport = &m.viewport - hdlModelGithubRepository.Viewport = &m.viewport - hdlModelWorkflowHistory.Viewport = &m.viewport - hdlModelWorkflow.Viewport = &m.viewport - hdlModelTrigger.Viewport = &m.viewport + m := model{ + viewport: vp, + timer: timer.NewWithInterval(1<<63-1, time.Millisecond*200), + modelHeader: hdlModelHeader, + modelInfo: hdlModelInfo, + SelectedRepository: selectedRepository, + modelGithubRepository: hdlModelGithubRepository, + modelWorkflowHistory: hdlModelWorkflowHistory, + modelWorkflow: hdlModelWorkflow, + modelTrigger: hdlModelTrigger, + keys: keys, + } return &m } func (m *model) Init() tea.Cmd { - m.viewport = viewport.Model{Width: minTerminalWidth, Height: minTerminalHeight} return tea.Batch( tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), @@ -145,60 +127,41 @@ func (m *model) View() string { var mainDoc strings.Builder var helpDoc string var operationDoc string - var helpDocHeight int - var width = lipgloss.Width(strings.Repeat("-", m.viewport.Width)) - 4 + var width = lipgloss.Width(strings.Repeat("-", m.viewport.Width)) - 5 hdltypes.ScreenWidth = &width - dynamicWindowStyle := ts.WindowStyleCyan.Width(width).Height(m.viewport.Height - 20) - + dynamicWindowStyle := ts.WindowStyleCyan.Width(width).Height(m.viewport.Height - 22) helpWindowStyle := ts.WindowStyleHelp.Width(width) - operationWindowStyle := lipgloss.NewStyle() + mainDoc.WriteString(m.modelHeader.View() + "\n") switch m.modelHeader.GetCurrentTab() { case 0: - mainDoc.WriteString("\n" + m.modelHeader.View() + "\n") - mainDoc.WriteString(dynamicWindowStyle.Render(m.modelInfo.View())) - operationDoc = operationWindowStyle.Render(m.actualModelInfo.ViewStatus()) - helpDoc = helpWindowStyle.Render(m.actualModelInfo.ViewHelp()) + operationDoc = m.modelInfo.ViewStatus() + helpDoc = helpWindowStyle.Render(m.modelInfo.ViewHelp()) case 1: - mainDoc.WriteString("\n" + m.modelHeader.View() + "\n") - mainDoc.WriteString(dynamicWindowStyle.Render(m.modelGithubRepository.View())) - operationDoc = operationWindowStyle.Render(m.actualModelGithubRepository.ViewStatus()) - helpDoc = helpWindowStyle.Render(m.actualModelGithubRepository.ViewHelp()) + operationDoc = m.modelGithubRepository.ViewStatus() + helpDoc = helpWindowStyle.Render(m.modelGithubRepository.ViewHelp()) case 2: - mainDoc.WriteString("\n" + m.modelHeader.View() + "\n") - mainDoc.WriteString(dynamicWindowStyle.Render(m.modelWorkflowHistory.View())) - operationDoc = operationWindowStyle.Render(m.directModelWorkflowHistory.ViewStatus()) - helpDoc = helpWindowStyle.Render(m.directModelWorkflowHistory.ViewHelp()) + operationDoc = m.modelWorkflowHistory.ViewStatus() + helpDoc = helpWindowStyle.Render(m.modelWorkflowHistory.ViewHelp()) case 3: - mainDoc.WriteString("\n" + m.modelHeader.View() + "\n") - mainDoc.WriteString(dynamicWindowStyle.Render(m.modelWorkflow.View())) - operationDoc = operationWindowStyle.Render(m.directModelWorkflow.ViewStatus()) - helpDoc = helpWindowStyle.Render(m.directModelWorkflow.ViewHelp()) + operationDoc = m.modelWorkflow.ViewStatus() + helpDoc = helpWindowStyle.Render(m.modelWorkflow.ViewHelp()) case 4: - mainDoc.WriteString("\n" + m.modelHeader.View() + "\n") - mainDoc.WriteString(dynamicWindowStyle.Render(m.modelTrigger.View())) - operationDoc = operationWindowStyle.Render(m.actualModelTrigger.ViewStatus()) - helpDoc = helpWindowStyle.Render(m.actualModelTrigger.ViewHelp()) + operationDoc = m.modelTrigger.ViewStatus() + helpDoc = helpWindowStyle.Render(m.modelTrigger.ViewHelp()) } mainDocContent := ts.DocStyle.Render(mainDoc.String()) - - mainDocHeight := strings.Count(mainDocContent, "\n") - helpDocHeight = strings.Count(helpDoc, "\n") - errorDocHeight := strings.Count(operationDoc, "\n") - requiredNewlinesForPadding := m.viewport.Height - mainDocHeight - helpDocHeight - errorDocHeight - padding := strings.Repeat("\n", max(0, requiredNewlinesForPadding)) - informationPane := lipgloss.JoinVertical(lipgloss.Top, operationDoc, helpDoc) - return mainDocContent + padding + informationPane + return lipgloss.JoinVertical(lipgloss.Top, mainDocContent, informationPane) } func (m *model) handleTabContent(cmd tea.Cmd, msg tea.Msg) tea.Cmd { diff --git a/internal/terminal/handler/header/header.go b/internal/terminal/handler/header/header.go index 74d310a..2cd294e 100644 --- a/internal/terminal/handler/header/header.go +++ b/internal/terminal/handler/header/header.go @@ -48,9 +48,10 @@ var ( ) // NewHeader returns a new Header. -func NewHeader() *Header { +func NewHeader(viewport *viewport.Model) *Header { once.Do(func() { h = &Header{ + Viewport: viewport, currentTab: 0, lockTabs: true, keys: keys, @@ -155,7 +156,7 @@ func (h *Header) View() string { var specialHeader string specialHeader = h.specialHeaders[0].header - renderedTitles := make([]string, 0, len(h.commonHeaders)) + var renderedTitles []string for i, title := range h.commonHeaders { if i == h.currentTab { renderedTitles = append(renderedTitles, title.activeStyle.Render(title.header)) diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index afb6510..055dc78 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -54,15 +54,16 @@ var ( applicationDescription string ) -func SetupModelInfo(githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { +func SetupModelInfo(viewport *viewport.Model, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { modelError := hdlerror.SetupModelError() - hdlModelHeader := header.NewHeader() + hdlModelHeader := header.NewHeader(viewport) s := spinner.New() s.Spinner = spinner.Pulse s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("120")) return &ModelInfo{ + Viewport: viewport, modelHeader: hdlModelHeader, github: githubUseCase, version: version, @@ -87,7 +88,7 @@ func (m *ModelInfo) checkUpdates(ctx context.Context) { if err != nil { m.modelError.SetError(err) m.modelError.SetErrorMessage("failed to check updates") - newVersionAvailableMsg = fmt.Sprintf("failed to check updates: %v\nPlease visit: %s", err, releaseURL) + newVersionAvailableMsg = fmt.Sprintf("failed to check updates.\nPlease visit: %s", releaseURL) return } @@ -98,7 +99,7 @@ func (m *ModelInfo) checkUpdates(ctx context.Context) { go m.Update(m) } -func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *ModelInfo) Update(msg tea.Msg) (*ModelInfo, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case tea.WindowSizeMsg: From c86f8420a473f78fbbe0aa4aaa4edaac5fe11b86 Mon Sep 17 00:00:00 2001 From: Engin Date: Thu, 27 Jun 2024 01:17:19 +0300 Subject: [PATCH 03/51] small improvements for better user experience --- internal/terminal/handler/ghtrigger/ghtrigger.go | 16 ++++++++-------- .../terminal/handler/ghworkflow/ghworkflow.go | 11 ++++++----- internal/terminal/handler/handler.go | 2 +- .../terminal/handler/information/information.go | 4 ++-- internal/terminal/style/style.go | 1 - 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index f05ec44..24e4177 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -340,12 +340,12 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { } func (m *ModelGithubTrigger) View() string { - baseStyle := lipgloss.NewStyle(). - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")) - - termWidth := m.Viewport.Width - termHeight := m.Viewport.Height + baseStyle := lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()) + if m.triggerFocused { + baseStyle = baseStyle.BorderForeground(lipgloss.Color("240")) + } else { + baseStyle = baseStyle.BorderForeground(lipgloss.Color("130")) + } var tableWidth int for _, t := range tableColumnsTrigger { @@ -353,7 +353,7 @@ func (m *ModelGithubTrigger) View() string { } newTableColumns := tableColumnsTrigger - widthDiff := termWidth - tableWidth + widthDiff := m.Viewport.Width - tableWidth if widthDiff > 0 { keyWidth := &newTableColumns[2].Width valueWidth := &newTableColumns[4].Width @@ -363,7 +363,7 @@ func (m *ModelGithubTrigger) View() string { *keyWidth = *valueWidth / 2 } m.tableTrigger.SetColumns(newTableColumns) - m.tableTrigger.SetHeight(termHeight - 17) + m.tableTrigger.SetHeight(m.Viewport.Height - 17) } doc := strings.Builder{} diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index 00a38e9..9b351ff 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -51,10 +51,6 @@ type ModelGithubWorkflow struct { actualModelGithubTrigger *ghtrigger.ModelGithubTrigger } -var baseStyle = lipgloss.NewStyle(). - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")) - func SetupModelGithubWorkflow(viewport *viewport.Model, githubUseCase gu.UseCase, selectedRepository *hdltypes.SelectedRepository) *ModelGithubWorkflow { var tableRowsTriggerableWorkflow []table.Row @@ -120,6 +116,10 @@ func (m *ModelGithubWorkflow) Update(msg tea.Msg) (*ModelGithubWorkflow, tea.Cmd } func (m *ModelGithubWorkflow) View() string { + var style = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")) + termWidth := m.Viewport.Width termHeight := m.Viewport.Height @@ -137,7 +137,8 @@ func (m *ModelGithubWorkflow) View() string { } doc := strings.Builder{} - doc.WriteString(baseStyle.Render(m.tableTriggerableWorkflow.View())) + doc.WriteString(style.Render(m.tableTriggerableWorkflow.View())) + doc.WriteString("\n\n\n") return doc.String() } diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index fec5745..c4333dc 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -131,7 +131,7 @@ func (m *model) View() string { var width = lipgloss.Width(strings.Repeat("-", m.viewport.Width)) - 5 hdltypes.ScreenWidth = &width - dynamicWindowStyle := ts.WindowStyleCyan.Width(width).Height(m.viewport.Height - 22) + dynamicWindowStyle := lipgloss.NewStyle().Width(width).Height(m.viewport.Height - 22) helpWindowStyle := ts.WindowStyleHelp.Width(width) mainDoc.WriteString(m.modelHeader.View() + "\n") diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index 055dc78..952b688 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -125,8 +125,8 @@ func (m *ModelInfo) View() string { infoDoc.WriteString(lipgloss.JoinVertical(lipgloss.Center, applicationName, applicationDescription, newVersionAvailableMsg)) - docHeight := strings.Count(infoDoc.String(), "\n") - requiredNewlinesForPadding := m.Viewport.Height - docHeight - 13 + docHeight := lipgloss.Height(infoDoc.String()) + requiredNewlinesForPadding := m.Viewport.Height - docHeight - 12 infoDoc.WriteString(strings.Repeat("\n", max(0, requiredNewlinesForPadding))) diff --git a/internal/terminal/style/style.go b/internal/terminal/style/style.go index c07089d..0fee68b 100644 --- a/internal/terminal/style/style.go +++ b/internal/terminal/style/style.go @@ -6,7 +6,6 @@ import ( var ( DocStyle = lipgloss.NewStyle().Padding(1, 2, 1, 2) - WindowStyleCyan = lipgloss.NewStyle().BorderForeground(lipgloss.Color("39")) WindowStyleOrange = lipgloss.NewStyle().BorderForeground(lipgloss.Color("#ffaf00")).Border(lipgloss.RoundedBorder()) WindowStyleRed = lipgloss.NewStyle().BorderForeground(lipgloss.Color("9")).Border(lipgloss.RoundedBorder()) WindowStyleGreen = lipgloss.NewStyle().BorderForeground(lipgloss.Color("10")).Border(lipgloss.RoundedBorder()) From ed66581ee9a44196ea2a6a7db3a2e6fada5f020f Mon Sep 17 00:00:00 2001 From: Engin Date: Thu, 27 Jun 2024 22:47:20 +0300 Subject: [PATCH 04/51] Make the code better --- internal/terminal/handler/handler.go | 28 +++--- internal/terminal/handler/header/header.go | 86 ++++++++++++------- .../handler/information/information.go | 64 +++++++------- 3 files changed, 98 insertions(+), 80 deletions(-) diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index c4333dc..dcd2167 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -2,12 +2,7 @@ package handler import ( "fmt" - "github.com/termkit/gama/internal/terminal/handler/header" - "strings" - "time" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/timer" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -16,10 +11,12 @@ import ( hdltrigger "github.com/termkit/gama/internal/terminal/handler/ghtrigger" hdlWorkflow "github.com/termkit/gama/internal/terminal/handler/ghworkflow" hdlworkflowhistory "github.com/termkit/gama/internal/terminal/handler/ghworkflowhistory" + "github.com/termkit/gama/internal/terminal/handler/header" hdlinfo "github.com/termkit/gama/internal/terminal/handler/information" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" ts "github.com/termkit/gama/internal/terminal/style" pkgversion "github.com/termkit/gama/pkg/version" + "strings" ) type model struct { @@ -28,7 +25,6 @@ type model struct { // models viewport *viewport.Model - timer timer.Model modelHeader *header.Header modelInfo *hdlinfo.ModelInfo @@ -68,7 +64,6 @@ func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Mod m := model{ viewport: vp, - timer: timer.NewWithInterval(1<<63-1, time.Millisecond*200), modelHeader: hdlModelHeader, modelInfo: hdlModelInfo, SelectedRepository: selectedRepository, @@ -86,7 +81,7 @@ func (m *model) Init() tea.Cmd { return tea.Batch( tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), - m.timer.Init(), + m.modelHeader.Init(), m.modelInfo.Init(), m.modelGithubRepository.Init(), m.modelWorkflowHistory.Init(), @@ -103,17 +98,22 @@ func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.viewport.Width = msg.Width m.viewport.Height = msg.Height case tea.KeyMsg: - m.modelHeader, cmd = m.modelHeader.Update(msg) - cmds = append(cmds, cmd) - cmds = append(cmds, m.handleTabContent(cmd, msg)) - + // Handle global keybindings switch { case key.Matches(msg, m.keys.Quit): return m, tea.Quit } - case timer.TickMsg: - m.timer, cmd = m.timer.Update(msg) + + m.modelHeader, cmd = m.modelHeader.Update(msg) + cmds = append(cmds, cmd) + 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)) } return m, tea.Batch(cmds...) diff --git a/internal/terminal/handler/header/header.go b/internal/terminal/handler/header/header.go index 2cd294e..1f9ca8a 100644 --- a/internal/terminal/handler/header/header.go +++ b/internal/terminal/handler/header/header.go @@ -5,8 +5,10 @@ import ( "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + ts "github.com/termkit/gama/internal/terminal/style" "strings" "sync" + "time" ) // Header is a helper for rendering the Header of the terminal. @@ -14,7 +16,8 @@ type Header struct { keys keyMap Viewport *viewport.Model - //timer timer.Model + + tickerInterval time.Duration currentTab int lockTabs bool @@ -51,10 +54,11 @@ var ( func NewHeader(viewport *viewport.Model) *Header { once.Do(func() { h = &Header{ - Viewport: viewport, - currentTab: 0, - lockTabs: true, - keys: keys, + tickerInterval: time.Millisecond * 250, + Viewport: viewport, + currentTab: 0, + lockTabs: true, + keys: keys, } }) return h @@ -94,9 +98,36 @@ func (h *Header) SetSpecialHeader(header string, firstStyle, secondStyle lipglos }) } +type UpdateMsg struct { + Msg string + UpdatingComponent string +} + func (h *Header) Init() tea.Cmd { - //return h.timer.Init() - return nil + return h.tick() +} + +func (h *Header) tick() tea.Cmd { + t := time.NewTimer(h.tickerInterval) + 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{ + Msg: "tick", + UpdatingComponent: "header", + } + } + } } func (h *Header) Update(msg tea.Msg) (*Header, tea.Cmd) { @@ -114,29 +145,10 @@ func (h *Header) Update(msg tea.Msg) (*Header, tea.Cmd) { h.currentTab = min(h.currentTab+1, len(h.commonHeaders)-1) } } + case UpdateMsg: + h.switchSpecialAnimation = !h.switchSpecialAnimation - //case timer.TickMsg: - //h.switchSpecialAnimation = !h.switchSpecialAnimation - - //var cmd tea.Cmd - //h.timer, cmd = h.timer.Update(msg) - //return h, cmd - // - //case timer.StartStopMsg: - // var cmd tea.Cmd - //h.timer, cmd = h.timer.Update(msg) - //return h, cmd - - //case tea.KeyMsg: - // switch { - // case key.Matches(msg, m.keymap.quit): - // m.quitting = true - // return m, tea.Quit - // case key.Matches(msg, m.keymap.reset): - // m.timer.Timeout = timeout - // case key.Matches(msg, m.keymap.start, m.keymap.stop): - // return m, m.timer.Toggle() - // } + return h, h.Init() } return h, nil @@ -158,14 +170,22 @@ func (h *Header) View() string { var renderedTitles []string for i, title := range h.commonHeaders { - if i == h.currentTab { - renderedTitles = append(renderedTitles, title.activeStyle.Render(title.header)) + if h.lockTabs { + if i == 0 { + renderedTitles = append(renderedTitles, title.activeStyle.Render(title.header)) + } else { + renderedTitles = append(renderedTitles, ts.TitleStyleDisabled.Render(title.header)) + } } else { - renderedTitles = append(renderedTitles, title.inactiveStyle.Render(title.header)) + if i == h.currentTab { + renderedTitles = append(renderedTitles, title.activeStyle.Render(title.header)) + } else { + renderedTitles = append(renderedTitles, title.inactiveStyle.Render(title.header)) + } } } - if h.currentTab == -1 { + if h.switchSpecialAnimation { specialHeader = h.specialHeaders[0].firstStyle.Render(h.specialHeaders[0].header) } else { specialHeader = h.specialHeaders[0].secondStyle.Render(h.specialHeaders[0].header) diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index 952b688..56af815 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -3,19 +3,17 @@ package information import ( "context" "fmt" - "github.com/termkit/gama/internal/terminal/handler/header" - "strings" - "time" - "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/spinner" "github.com/charmbracelet/bubbles/viewport" 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" + "github.com/termkit/gama/internal/terminal/handler/header" pkgversion "github.com/termkit/gama/pkg/version" + "strings" + "time" ) type ModelInfo struct { @@ -24,6 +22,8 @@ type ModelInfo struct { // use cases github gu.UseCase + complete bool + // models modelHeader *header.Header @@ -37,6 +37,8 @@ type ModelInfo struct { } const ( + spinnerInterval = 100 * time.Millisecond + releaseURL = "https://github.com/termkit/gama/releases" applicationName = ` @@ -59,7 +61,7 @@ func SetupModelInfo(viewport *viewport.Model, githubUseCase gu.UseCase, version hdlModelHeader := header.NewHeader(viewport) s := spinner.New() - s.Spinner = spinner.Pulse + s.Spinner = spinner.Dot s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("120")) return &ModelInfo{ @@ -74,13 +76,25 @@ func SetupModelInfo(viewport *viewport.Model, githubUseCase gu.UseCase, version } } +type UpdateSpinnerMsg string + func (m *ModelInfo) Init() tea.Cmd { currentVersion = m.version.CurrentVersion() applicationDescription = fmt.Sprintf("Github Actions Manager (%s)", currentVersion) go m.testConnection(context.Background()) go m.checkUpdates(context.Background()) - return nil + return m.tickSpinner() +} + +func (m *ModelInfo) tickSpinner() tea.Cmd { + t := time.NewTimer(spinnerInterval) + return func() tea.Msg { + select { + case <-t.C: + return UpdateSpinnerMsg("tick") + } + } } func (m *ModelInfo) checkUpdates(ctx context.Context) { @@ -101,14 +115,15 @@ func (m *ModelInfo) checkUpdates(ctx context.Context) { func (m *ModelInfo) Update(msg tea.Msg) (*ModelInfo, tea.Cmd) { var cmd tea.Cmd - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.Help.Width = msg.Width - case tea.KeyMsg: - switch { - case key.Matches(msg, m.Keys.Quit): - return m, tea.Quit + switch msg.(type) { + case UpdateSpinnerMsg: + if m.complete { + return m, nil } + + m.modelError.SetProgressMessage("Checking your token " + m.spinner.View()) + m.spinner, cmd = m.spinner.Update(m.spinner.Tick()) + return m, m.tickSpinner() } return m, cmd @@ -134,23 +149,7 @@ func (m *ModelInfo) View() string { } func (m *ModelInfo) testConnection(ctx context.Context) { - ctxWithCancel, cancel := context.WithCancel(ctx) - - // TODO: make it better - go func(ctx context.Context) { - for { - select { - case <-ctx.Done(): - return - default: - m.spinner, _ = m.spinner.Update(m.spinner.Tick()) - m.modelError.SetProgressMessage("Checking your token " + m.spinner.View()) - time.Sleep(200 * time.Millisecond) - } - } - }(ctxWithCancel) - defer cancel() - + time.Sleep(time.Second * 5) // TODO : remove, just for testing spinner _, err := m.github.GetAuthUser(ctx) if err != nil { m.modelError.SetError(err) @@ -162,8 +161,7 @@ func (m *ModelInfo) testConnection(ctx context.Context) { m.modelError.Reset() m.modelError.SetSuccessMessage("Welcome to GAMA!") m.modelHeader.SetLockTabs(false) - - go m.Update(m) + m.complete = true } func (m *ModelInfo) ViewStatus() string { From 84c66e2f1ec6c6b957475089033eeff9d2fda429 Mon Sep 17 00:00:00 2001 From: Engin Date: Sat, 29 Jun 2024 14:14:34 +0300 Subject: [PATCH 05/51] 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), From 0080dcbd6156d48e5fd4acb03f92418ef74501b1 Mon Sep 17 00:00:00 2001 From: Engin Date: Sun, 14 Jul 2024 23:00:50 +0300 Subject: [PATCH 06/51] Refactor the code --- internal/terminal/handler/error/error.go | 12 +- .../handler/ghrepository/ghrepository.go | 57 +++--- .../terminal/handler/ghtrigger/ghtrigger.go | 20 +-- .../terminal/handler/ghworkflow/ghworkflow.go | 7 +- .../ghworkflowhistory/ghworkflowhistory.go | 14 +- internal/terminal/handler/handler.go | 162 ++++-------------- internal/terminal/handler/header/header.go | 32 ++-- .../handler/information/information.go | 24 +-- internal/terminal/handler/skeleton/keymap.go | 32 ++++ .../terminal/handler/skeleton/skeleton.go | 120 +++++++++++++ internal/terminal/handler/spirit/spirit.go | 28 +++ .../terminal/handler/taboptions/taboptions.go | 1 + internal/terminal/handler/types/types.go | 2 - 13 files changed, 308 insertions(+), 203 deletions(-) create mode 100644 internal/terminal/handler/skeleton/keymap.go create mode 100644 internal/terminal/handler/skeleton/skeleton.go create mode 100644 internal/terminal/handler/spirit/spirit.go diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/error/error.go index 79a69a5..9f167e4 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/error/error.go @@ -45,23 +45,23 @@ func SetupModelError() ModelError { func (m *ModelError) View() string { var windowStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder()) - + width := hdltypes.NewTerminalViewport().Width - 4 doc := strings.Builder{} if m.HaveError() { - windowStyle = ts.WindowStyleError.Width(*hdltypes.ScreenWidth) + windowStyle = ts.WindowStyleError.Width(width) doc.WriteString(windowStyle.Render(m.ViewError())) return doc.String() } switch m.messageType { case MessageTypeDefault: - windowStyle = ts.WindowStyleDefault.Width(*hdltypes.ScreenWidth) + windowStyle = ts.WindowStyleDefault.Width(width) case MessageTypeProgress: - windowStyle = ts.WindowStyleProgress.Width(*hdltypes.ScreenWidth) + windowStyle = ts.WindowStyleProgress.Width(width) case MessageTypeSuccess: - windowStyle = ts.WindowStyleSuccess.Width(*hdltypes.ScreenWidth) + windowStyle = ts.WindowStyleSuccess.Width(width) default: - windowStyle = ts.WindowStyleDefault.Width(*hdltypes.ScreenWidth) + windowStyle = ts.WindowStyleDefault.Width(width) } doc.WriteString(windowStyle.Render(m.ViewMessage())) diff --git a/internal/terminal/handler/ghrepository/ghrepository.go b/internal/terminal/handler/ghrepository/ghrepository.go index 1b3c599..d7cb723 100644 --- a/internal/terminal/handler/ghrepository/ghrepository.go +++ b/internal/terminal/handler/ghrepository/ghrepository.go @@ -4,9 +4,6 @@ import ( "context" "errors" "fmt" - "strconv" - "strings" - "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/table" @@ -19,7 +16,11 @@ import ( hdlerror "github.com/termkit/gama/internal/terminal/handler/error" "github.com/termkit/gama/internal/terminal/handler/taboptions" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" + ts "github.com/termkit/gama/internal/terminal/style" "github.com/termkit/gama/pkg/browser" + "strconv" + "strings" + "time" ) type ModelGithubRepository struct { @@ -49,10 +50,6 @@ type ModelGithubRepository struct { textInput textinput.Model } -var baseStyle = lipgloss.NewStyle(). - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")) - func SetupModelGithubRepository(githubUseCase gu.UseCase) *ModelGithubRepository { var tableRowsGithubRepository []table.Row @@ -127,9 +124,19 @@ func SetupModelGithubRepository(githubUseCase gu.UseCase) *ModelGithubRepository } } -func (m *ModelGithubRepository) Init() tea.Cmd { - go m.syncRepositories(m.syncRepositoriesContext) +type UpdateSpinnerMsg string +func (m *ModelGithubRepository) tickSpinner() tea.Cmd { + t := time.NewTimer(time.Millisecond * 200) + return func() tea.Msg { + select { + case <-t.C: + return UpdateSpinnerMsg("tick") + } + } +} + +func (m *ModelGithubRepository) Init() tea.Cmd { openInBrowser := func() { m.modelError.SetProgressMessage("Opening in browser...") @@ -145,10 +152,10 @@ func (m *ModelGithubRepository) Init() tea.Cmd { m.modelTabOptions.AddOption("Open in browser", openInBrowser) - return tea.Batch(m.modelTabOptions.Init()) + return tea.Batch(m.modelTabOptions.Init(), m.syncRepositories(m.syncRepositoriesContext), m.tickSpinner()) } -func (m *ModelGithubRepository) Update(msg tea.Msg) (*ModelGithubRepository, tea.Cmd) { +func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd var cmd tea.Cmd @@ -169,6 +176,8 @@ func (m *ModelGithubRepository) Update(msg tea.Msg) (*ModelGithubRepository, tea m.searchTableGithubRepository.GotoTop() m.searchTableGithubRepository.SetCursor(0) } + case UpdateSpinnerMsg: + return m, m.tickSpinner() } m.textInput, cmd = m.textInput.Update(textInputMsg) @@ -191,8 +200,11 @@ func (m *ModelGithubRepository) Update(msg tea.Msg) (*ModelGithubRepository, tea } func (m *ModelGithubRepository) View() string { - termWidth := m.Viewport.Width - termHeight := m.Viewport.Height + var baseStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")).MarginLeft(1) + + helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) var tableWidth int for _, t := range tableColumnsGithubRepository { @@ -200,20 +212,20 @@ func (m *ModelGithubRepository) View() string { } newTableColumns := tableColumnsGithubRepository - widthDiff := termWidth - tableWidth + widthDiff := m.Viewport.Width - tableWidth if widthDiff > 0 { - newTableColumns[0].Width += widthDiff - 15 + newTableColumns[0].Width += widthDiff - 12 m.tableGithubRepository.SetColumns(newTableColumns) - m.tableGithubRepository.SetHeight(termHeight - 20) + m.tableGithubRepository.SetHeight(m.Viewport.Height - 19) } doc := strings.Builder{} doc.WriteString(baseStyle.Render(m.tableGithubRepository.View())) - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.viewSearchBar(), m.modelTabOptions.View()) + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.viewSearchBar(), m.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) } -func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { +func (m *ModelGithubRepository) syncRepositories(ctx context.Context) tea.Cmd { m.modelError.ResetError() // reset previous errors m.modelTabOptions.SetStatus(taboptions.OptionWait) m.modelError.SetProgressMessage("Fetching repositories...") @@ -228,18 +240,18 @@ func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { Sort: domain.SortByUpdated, }) if errors.Is(err, context.Canceled) { - return + return nil } else if err != nil { m.modelError.SetError(err) m.modelError.SetErrorMessage("Repositories cannot be listed") - return + return nil } if len(repositories.Repositories) == 0 { m.modelTabOptions.SetStatus(taboptions.OptionNone) m.modelError.SetDefaultMessage("No repositories found") m.textInput.Blur() - return + return nil } tableRowsGithubRepository := make([]table.Row, 0, len(repositories.Repositories)) @@ -260,6 +272,7 @@ func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { m.textInput.Focus() m.modelError.SetSuccessMessage("Repositories fetched") go m.Update(m) // update model + return nil } func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { @@ -284,7 +297,7 @@ func (m *ModelGithubRepository) viewSearchBar() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(*hdltypes.ScreenWidth - 2) + Width(m.Viewport.Width - 4).MarginLeft(1) // Build the options list doc := strings.Builder{} diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index c0d5a5f..adbdb12 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -15,6 +15,7 @@ import ( "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" + ts "github.com/termkit/gama/internal/terminal/style" "github.com/termkit/gama/pkg/workflow" "slices" "strings" @@ -101,7 +102,7 @@ func (m *ModelGithubTrigger) Init() tea.Cmd { return tea.Batch(textinput.Blink) } -func (m *ModelGithubTrigger) Update(msg tea.Msg) (*ModelGithubTrigger, tea.Cmd) { +func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.SelectedRepository.WorkflowName == "" { m.modelError.Reset() m.modelError.SetDefaultMessage("No workflow selected.") @@ -342,6 +343,8 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { func (m *ModelGithubTrigger) View() string { baseStyle := lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()) + helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) + if m.triggerFocused { baseStyle = baseStyle.BorderForeground(lipgloss.Color("240")) } else { @@ -381,7 +384,7 @@ func (m *ModelGithubTrigger) View() string { } return lipgloss.JoinVertical(lipgloss.Top, doc.String(), - lipgloss.JoinHorizontal(lipgloss.Top, selector, m.triggerButton())) + lipgloss.JoinHorizontal(lipgloss.Top, selector, m.triggerButton()), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { @@ -619,7 +622,7 @@ func (m *ModelGithubTrigger) emptySelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(*hdltypes.ScreenWidth - 13) + Width(m.Viewport.Width - 13) // Build the options list doc := strings.Builder{} @@ -632,14 +635,9 @@ func (m *ModelGithubTrigger) inputSelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(*hdltypes.ScreenWidth - 13) - - // Build the options list - doc := strings.Builder{} - - doc.WriteString(m.textInput.View()) + Width(m.Viewport.Width - 13).MarginLeft(2) - return windowStyle.Render(doc.String()) + return windowStyle.Render(m.textInput.View()) } // optionSelector renders the options list @@ -649,7 +647,7 @@ func (m *ModelGithubTrigger) optionSelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(*hdltypes.ScreenWidth - 13) + Width(m.Viewport.Width - 13).MarginLeft(1) // Define styles for selected and unselected options selectedOptionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("120")).Padding(0, 1) diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index 35ce66e..d38384b 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + ts "github.com/termkit/gama/internal/terminal/style" "sort" "strings" @@ -87,7 +88,7 @@ func (m *ModelGithubWorkflow) Init() tea.Cmd { return nil } -func (m *ModelGithubWorkflow) Update(msg tea.Msg) (*ModelGithubWorkflow, tea.Cmd) { +func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd if m.lastRepository != m.SelectedRepository.RepositoryName { @@ -112,6 +113,8 @@ func (m *ModelGithubWorkflow) View() string { BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("240")) + helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) + termWidth := m.Viewport.Width termHeight := m.Viewport.Height @@ -132,7 +135,7 @@ func (m *ModelGithubWorkflow) View() string { doc.WriteString(style.Render(m.tableTriggerableWorkflow.View())) doc.WriteString("\n\n\n") - return doc.String() + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go index e136e0c..0f9be6a 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + ts "github.com/termkit/gama/internal/terminal/style" "strings" "time" @@ -173,7 +174,7 @@ func (m *ModelGithubWorkflowHistory) setupOptions() { m.modelTabOptions.AddOption("Cancel workflow", cancelWorkflow) } -func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (*ModelGithubWorkflowHistory, tea.Cmd) { +func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.lastRepository != m.SelectedRepository.RepositoryName { m.tableReady = false m.cancelSyncWorkflowHistory() // cancel previous sync @@ -269,7 +270,8 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { func (m *ModelGithubWorkflowHistory) View() string { var baseStyle = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")) + BorderForeground(lipgloss.Color("240")).MarginLeft(1) + helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) termWidth := m.Viewport.Width termHeight := m.Viewport.Height @@ -284,20 +286,20 @@ func (m *ModelGithubWorkflowHistory) View() string { if widthDiff > 0 { if m.updateRound%2 == 0 { - newTableColumns[0].Width += widthDiff - 19 + newTableColumns[0].Width += widthDiff - 16 } else { - newTableColumns[1].Width += widthDiff - 19 + newTableColumns[1].Width += widthDiff - 16 } m.updateRound++ m.tableWorkflowHistory.SetColumns(newTableColumns) } - m.tableWorkflowHistory.SetHeight(termHeight - 17) + m.tableWorkflowHistory.SetHeight(termHeight - 16) doc := strings.Builder{} doc.WriteString(baseStyle.Render(m.tableWorkflowHistory.View())) - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View()) + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubWorkflowHistory) ViewStatus() string { diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index 4802aab..2f6ba76 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -2,68 +2,61 @@ package handler import ( "fmt" - "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/viewport" 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" hdlworkflowhistory "github.com/termkit/gama/internal/terminal/handler/ghworkflowhistory" - "github.com/termkit/gama/internal/terminal/handler/header" hdlinfo "github.com/termkit/gama/internal/terminal/handler/information" + hdlskeleton "github.com/termkit/gama/internal/terminal/handler/skeleton" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" ts "github.com/termkit/gama/internal/terminal/style" pkgversion "github.com/termkit/gama/pkg/version" - "strings" - "time" ) type model struct { // models - viewport *viewport.Model - - modelError *hdlerror.ModelError - modelHeader *header.Header - modelInfo *hdlinfo.ModelInfo - modelGithubRepository *hdlgithubrepo.ModelGithubRepository - modelWorkflow *hdlWorkflow.ModelGithubWorkflow - modelWorkflowHistory *hdlworkflowhistory.ModelGithubWorkflowHistory - modelTrigger *hdltrigger.ModelGithubTrigger + viewport *viewport.Model + modelSkeleton *hdlskeleton.Skeleton // keymap keys keyMap } func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Model { - // setup models - 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", time.Millisecond*500, ts.TitleStyleLiveModeOn, ts.TitleStyleLiveModeOff, ts.TitleStyleDisabled) + skeleton := hdlskeleton.NewSkeleton() + + skeleton.AddPage(hdlskeleton.Title{Title: "Info", Style: hdlskeleton.TitleStyle{ + Active: ts.TitleStyleActive, + Inactive: ts.TitleStyleInactive, + }}, hdlinfo.SetupModelInfo(githubUseCase, version)) + + skeleton.AddPage(hdlskeleton.Title{Title: "Repository", Style: hdlskeleton.TitleStyle{ + Active: ts.TitleStyleActive, + Inactive: ts.TitleStyleInactive, + }}, hdlgithubrepo.SetupModelGithubRepository(githubUseCase)) + + skeleton.AddPage(hdlskeleton.Title{Title: "Workflow History", Style: hdlskeleton.TitleStyle{ + Active: ts.TitleStyleActive, + Inactive: ts.TitleStyleInactive, + }}, hdlworkflowhistory.SetupModelGithubWorkflowHistory(githubUseCase)) + + skeleton.AddPage(hdlskeleton.Title{Title: "Workflow", Style: hdlskeleton.TitleStyle{ + Active: ts.TitleStyleActive, + Inactive: ts.TitleStyleInactive, + }}, hdlWorkflow.SetupModelGithubWorkflow(githubUseCase)) + + skeleton.AddPage(hdlskeleton.Title{Title: "Trigger", Style: hdlskeleton.TitleStyle{ + Active: ts.TitleStyleActive, + Inactive: ts.TitleStyleInactive, + }}, hdltrigger.SetupModelGithubTrigger(githubUseCase)) m := model{ - viewport: hdltypes.NewTerminalViewport(), - modelError: &hdlModelError, - modelHeader: hdlModelHeader, - modelInfo: hdlModelInfo, - modelGithubRepository: hdlModelGithubRepository, - modelWorkflowHistory: hdlModelWorkflowHistory, - modelWorkflow: hdlModelWorkflow, - modelTrigger: hdlModelTrigger, - keys: keys, + viewport: hdltypes.NewTerminalViewport(), + modelSkeleton: skeleton, + keys: keys, } return &m @@ -73,42 +66,16 @@ func (m *model) Init() tea.Cmd { return tea.Batch( tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), - m.modelHeader.Init(), - m.modelInfo.Init(), - m.modelGithubRepository.Init(), - m.modelWorkflowHistory.Init(), - m.modelWorkflow.Init(), - m.modelTrigger.Init()) + m.modelSkeleton.Init(), + ) } func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd var cmds []tea.Cmd - switch msg := msg.(type) { - case tea.WindowSizeMsg: - m.viewport.Width = msg.Width - m.viewport.Height = msg.Height - case tea.KeyMsg: - // Handle global keybindings - switch { - case key.Matches(msg, m.keys.Quit): - return m, tea.Quit - } - - 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) - case hdlworkflowhistory.UpdateWorkflowHistoryMsg: - m.modelWorkflowHistory, cmd = m.modelWorkflowHistory.Update(msg) - cmds = append(cmds, cmd) - } + var cmd tea.Cmd + m.modelSkeleton, cmd = m.modelSkeleton.Update(msg) + cmds = append(cmds, cmd) return m, tea.Batch(cmds...) } @@ -118,58 +85,5 @@ func (m *model) View() string { return fmt.Sprintf("Terminal window is too small. Please resize to at least %dx%d.", hdltypes.MinTerminalWidth, hdltypes.MinTerminalHeight) } - var mainDoc strings.Builder - var helpDoc string - var operationDoc string - - var width = lipgloss.Width(strings.Repeat("-", m.viewport.Width)) - 5 - hdltypes.ScreenWidth = &width - - dynamicWindowStyle := lipgloss.NewStyle().Width(width).Height(m.viewport.Height - 22) - helpWindowStyle := ts.WindowStyleHelp.Width(width) - - mainDoc.WriteString(m.modelHeader.View() + "\n") - switch m.modelHeader.GetCurrentTab() { - case 0: - mainDoc.WriteString(dynamicWindowStyle.Render(m.modelInfo.View())) - operationDoc = m.modelInfo.ViewStatus() - helpDoc = helpWindowStyle.Render(m.modelInfo.ViewHelp()) - case 1: - mainDoc.WriteString(dynamicWindowStyle.Render(m.modelGithubRepository.View())) - operationDoc = m.modelGithubRepository.ViewStatus() - helpDoc = helpWindowStyle.Render(m.modelGithubRepository.ViewHelp()) - case 2: - mainDoc.WriteString(dynamicWindowStyle.Render(m.modelWorkflowHistory.View())) - operationDoc = m.modelWorkflowHistory.ViewStatus() - helpDoc = helpWindowStyle.Render(m.modelWorkflowHistory.ViewHelp()) - case 3: - mainDoc.WriteString(dynamicWindowStyle.Render(m.modelWorkflow.View())) - operationDoc = m.modelWorkflow.ViewStatus() - helpDoc = helpWindowStyle.Render(m.modelWorkflow.ViewHelp()) - case 4: - mainDoc.WriteString(dynamicWindowStyle.Render(m.modelTrigger.View())) - operationDoc = m.modelTrigger.ViewStatus() - helpDoc = helpWindowStyle.Render(m.modelTrigger.ViewHelp()) - } - - mainDocContent := ts.DocStyle.Render(mainDoc.String()) - informationPane := lipgloss.JoinVertical(lipgloss.Top, operationDoc, helpDoc) - - return lipgloss.JoinVertical(lipgloss.Top, mainDocContent, informationPane) -} - -func (m *model) handleTabContent(cmd tea.Cmd, msg tea.Msg) tea.Cmd { - switch m.modelHeader.GetCurrentTab() { - case 0: - m.modelInfo, cmd = m.modelInfo.Update(msg) - case 1: - m.modelGithubRepository, cmd = m.modelGithubRepository.Update(msg) - case 2: - m.modelWorkflowHistory, cmd = m.modelWorkflowHistory.Update(msg) - case 3: - m.modelWorkflow, cmd = m.modelWorkflow.Update(msg) - case 4: - m.modelTrigger, cmd = m.modelTrigger.Update(msg) - } - return cmd + return m.modelSkeleton.View() } diff --git a/internal/terminal/handler/header/header.go b/internal/terminal/handler/header/header.go index d731bcb..90cd12f 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/spirit" "github.com/termkit/gama/internal/terminal/handler/types" ts "github.com/termkit/gama/internal/terminal/style" "strings" @@ -19,7 +20,8 @@ type Header struct { keys keyMap currentTab int - lockTabs bool + + modelSpirit *spirit.ModelSpirit commonHeaders []commonHeader specialHeader specialHeader @@ -51,11 +53,13 @@ var ( // NewHeader returns a new Header. func NewHeader() *Header { once.Do(func() { + s := spirit.NewSpirit() + s.SetLockTabs(false) h = &Header{ + modelSpirit: s, specialHeaderInterval: time.Millisecond * 100, Viewport: types.NewTerminalViewport(), currentTab: 0, - lockTabs: true, keys: keys, } }) @@ -70,15 +74,7 @@ func (h *Header) GetCurrentTab() int { return h.currentTab } -func (h *Header) SetLockTabs(lock bool) { - h.lockTabs = lock -} - -func (h *Header) GetLockTabs() bool { - return h.lockTabs -} - -func (h *Header) AddCommonHeader(header string, inactiveStyle, activeStyle lipgloss.Style) { +func (h *Header) AddCommonHeader(header string, activeStyle, inactiveStyle lipgloss.Style) { h.commonHeaders = append(h.commonHeaders, commonHeader{ header: header, rawHeader: header, @@ -126,11 +122,11 @@ func (h *Header) Update(msg tea.Msg) (*Header, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, h.keys.SwitchTabLeft): - if !h.lockTabs { + if !h.modelSpirit.GetLockTabs() { h.currentTab = max(h.currentTab-1, 0) } case key.Matches(msg, h.keys.SwitchTabRight): - if !h.lockTabs { + if !h.modelSpirit.GetLockTabs() { h.currentTab = min(h.currentTab+1, len(h.commonHeaders)-1) } } @@ -158,11 +154,10 @@ func (h *Header) View() string { titles += "LLL RRR" } titleLen := len(titles) - specialTitleLen := len(h.specialHeader.header) var renderedTitles []string for i, title := range h.commonHeaders { - if h.lockTabs { + if h.modelSpirit.GetLockTabs() { if i == 0 { renderedTitles = append(renderedTitles, title.activeStyle.Render(title.header)) } else { @@ -177,10 +172,7 @@ func (h *Header) View() string { } } - var specialHeader string - specialHeader = h.specialHeader.styles[h.currentSpecialStyle].Render(h.specialHeader.header) - - line := strings.Repeat("─", h.Viewport.Width-(titleLen+specialTitleLen)) + line := strings.Repeat("─", h.Viewport.Width-(titleLen)+10) - return lipgloss.JoinHorizontal(lipgloss.Center, append(renderedTitles, line, specialHeader)...) + return lipgloss.JoinHorizontal(lipgloss.Center, append(renderedTitles, line)...) } diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index debca30..ec11ec2 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -10,8 +10,9 @@ 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/header" + "github.com/termkit/gama/internal/terminal/handler/spirit" "github.com/termkit/gama/internal/terminal/handler/types" + ts "github.com/termkit/gama/internal/terminal/style" pkgversion "github.com/termkit/gama/pkg/version" "strings" "time" @@ -26,7 +27,7 @@ type ModelInfo struct { complete bool // models - modelHeader *header.Header + modelSpirit *spirit.ModelSpirit Help help.Model Viewport *viewport.Model @@ -59,7 +60,7 @@ var ( func SetupModelInfo(githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { modelError := hdlerror.SetupModelError() - hdlModelHeader := header.NewHeader() + //hdlModelHeader := header.NewHeader() s := spinner.New() s.Spinner = spinner.Dot @@ -67,7 +68,7 @@ func SetupModelInfo(githubUseCase gu.UseCase, version pkgversion.Version) *Model return &ModelInfo{ Viewport: types.NewTerminalViewport(), - modelHeader: hdlModelHeader, + modelSpirit: spirit.NewSpirit(), github: githubUseCase, version: version, Help: help.New(), @@ -114,7 +115,7 @@ func (m *ModelInfo) checkUpdates(ctx context.Context) { go m.Update(m) } -func (m *ModelInfo) Update(msg tea.Msg) (*ModelInfo, tea.Cmd) { +func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg.(type) { case UpdateSpinnerMsg: @@ -137,30 +138,33 @@ func (m *ModelInfo) View() string { BorderForeground(lipgloss.Color("39")). Align(lipgloss.Center). Border(lipgloss.RoundedBorder()). - Width(m.Viewport.Width - 7) + Width(m.Viewport.Width - 3) + + helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) infoDoc.WriteString(lipgloss.JoinVertical(lipgloss.Center, applicationName, applicationDescription, newVersionAvailableMsg)) docHeight := lipgloss.Height(infoDoc.String()) - requiredNewlinesForPadding := m.Viewport.Height - docHeight - 12 + requiredNewlinesForPadding := m.Viewport.Height - docHeight - 11 infoDoc.WriteString(strings.Repeat("\n", max(0, requiredNewlinesForPadding))) - return ws.Render(infoDoc.String()) + return lipgloss.JoinVertical(lipgloss.Top, ws.Render(infoDoc.String()), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelInfo) testConnection(ctx context.Context) { + //time.Sleep(3 * time.Second) _, err := m.github.GetAuthUser(ctx) if err != nil { m.modelError.SetError(err) m.modelError.SetErrorMessage("failed to test connection, please check your token&permission") - m.modelHeader.SetLockTabs(true) + m.modelSpirit.SetLockTabs(true) return } m.modelError.Reset() m.modelError.SetSuccessMessage("Welcome to GAMA!") - m.modelHeader.SetLockTabs(false) + m.modelSpirit.SetLockTabs(false) m.complete = true } diff --git a/internal/terminal/handler/skeleton/keymap.go b/internal/terminal/handler/skeleton/keymap.go new file mode 100644 index 0000000..2443d47 --- /dev/null +++ b/internal/terminal/handler/skeleton/keymap.go @@ -0,0 +1,32 @@ +package skeleton + +import ( + "fmt" + "github.com/termkit/gama/internal/config" + + teakey "github.com/charmbracelet/bubbles/key" +) + +type keyMap struct { + SwitchTabRight teakey.Binding + SwitchTabLeft teakey.Binding + Quit teakey.Binding +} + +var keys = func() keyMap { + cfg, err := config.LoadConfig() + if err != nil { + panic(fmt.Sprintf("failed to load config: %v", err)) + } + return keyMap{ + SwitchTabRight: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.SwitchTabRight), + ), + SwitchTabLeft: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.SwitchTabLeft), + ), + Quit: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.Quit), + ), + } +}() diff --git a/internal/terminal/handler/skeleton/skeleton.go b/internal/terminal/handler/skeleton/skeleton.go new file mode 100644 index 0000000..9ebcf74 --- /dev/null +++ b/internal/terminal/handler/skeleton/skeleton.go @@ -0,0 +1,120 @@ +package skeleton + +import ( + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/termkit/gama/internal/terminal/handler/header" + "github.com/termkit/gama/internal/terminal/handler/spirit" + "github.com/termkit/gama/internal/terminal/handler/types" + "sync" +) + +// Skeleton is a helper for rendering the Skeleton of the terminal. +type Skeleton struct { + Viewport *viewport.Model + + header *header.Header + modelSpirit *spirit.ModelSpirit + lockTabs bool + + keys keyMap + + currentTab int + Pages []tea.Model +} + +func (s *Skeleton) AddPage(title Title, page tea.Model) { + s.header.AddCommonHeader(title.Title, title.Style.Active, title.Style.Inactive) + s.Pages = append(s.Pages, page) +} + +type Title struct { + Title string + Style TitleStyle +} + +type TitleStyle struct { + Active lipgloss.Style + Inactive lipgloss.Style +} + +var ( + once sync.Once + s *Skeleton +) + +// NewSkeleton returns a new Skeleton. +func NewSkeleton() *Skeleton { + once.Do(func() { + s = &Skeleton{ + Viewport: types.NewTerminalViewport(), + header: header.NewHeader(), + modelSpirit: spirit.NewSpirit(), + keys: keys, + } + }) + return s +} + +type SwitchTab struct { + Tab int +} + +func (s *Skeleton) SetCurrentTab(tab int) { + s.currentTab = tab +} + +func (s *Skeleton) Init() tea.Cmd { + self := func() tea.Msg { + return SwitchTab{} + } + + inits := make([]tea.Cmd, len(s.Pages)+1) // +1 for self + for i := range s.Pages { + inits[i] = s.Pages[i].Init() + } + + inits[len(s.Pages)] = self + + return tea.Batch(inits...) +} + +func (s *Skeleton) Update(msg tea.Msg) (*Skeleton, tea.Cmd) { + switch msg := msg.(type) { + case tea.WindowSizeMsg: + s.Viewport.Width = msg.Width + s.Viewport.Height = msg.Height + //case SwitchTab: + // s.SetCurrentTab(msg.Tab) + // s.header.SetCurrentTab(msg.Tab) + // + // var cmd tea.Cmd + // s.Pages[s.currentTab], cmd = s.Pages[msg.Tab].Update(msg) + // return s, cmd + case tea.KeyMsg: + switch { + case key.Matches(msg, s.keys.Quit): + return s, tea.Quit + case key.Matches(msg, s.keys.SwitchTabLeft): + if !s.modelSpirit.GetLockTabs() { + s.currentTab = max(s.currentTab-1, 0) + } + case key.Matches(msg, s.keys.SwitchTabRight): + if !s.modelSpirit.GetLockTabs() { + s.currentTab = min(s.currentTab+1, len(s.Pages)-1) + } + } + } + + var cmd tea.Cmd + s.header, cmd = s.header.Update(msg) + s.Pages[s.currentTab], cmd = s.Pages[s.currentTab].Update(msg) + + return s, cmd +} + +func (s *Skeleton) View() string { + return lipgloss.JoinVertical(lipgloss.Top, s.header.View(), s.Pages[s.currentTab].View()) +} diff --git a/internal/terminal/handler/spirit/spirit.go b/internal/terminal/handler/spirit/spirit.go new file mode 100644 index 0000000..b635325 --- /dev/null +++ b/internal/terminal/handler/spirit/spirit.go @@ -0,0 +1,28 @@ +package spirit + +import "sync" + +type ModelSpirit struct { + lockTabs bool +} + +var ( + once sync.Once + s *ModelSpirit +) + +// NewSpirit returns a new Spirit. +func NewSpirit() *ModelSpirit { + once.Do(func() { + s = &ModelSpirit{} + }) + return s +} + +func (s *ModelSpirit) SetLockTabs(lock bool) { + s.lockTabs = lock +} + +func (s *ModelSpirit) GetLockTabs() bool { + return s.lockTabs +} diff --git a/internal/terminal/handler/taboptions/taboptions.go b/internal/terminal/handler/taboptions/taboptions.go index b37c6e7..409cc40 100644 --- a/internal/terminal/handler/taboptions/taboptions.go +++ b/internal/terminal/handler/taboptions/taboptions.go @@ -114,6 +114,7 @@ func (o *Options) View() string { var style = o.Style.Foreground(lipgloss.Color("15")) var opts []string + opts = append(opts, " ") for i, option := range o.optionsAction { switch o.status { diff --git a/internal/terminal/handler/types/types.go b/internal/terminal/handler/types/types.go index 33f3087..8f42c6f 100644 --- a/internal/terminal/handler/types/types.go +++ b/internal/terminal/handler/types/types.go @@ -24,8 +24,6 @@ func NewSelectedRepository() *SelectedRepository { return selectedRepository } -var ScreenWidth *int - // ---------------------------------------------- const ( From 1d3487b99b044eb5c4c8a22e1d8ef76d0e937087 Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 31 Jul 2024 00:56:35 +0300 Subject: [PATCH 07/51] Architectural changes --- internal/terminal/handler/error/error.go | 106 ++++++++++++++++-- .../handler/ghrepository/ghrepository.go | 45 +++++--- .../terminal/handler/ghtrigger/ghtrigger.go | 12 +- .../terminal/handler/ghworkflow/ghworkflow.go | 9 +- .../ghworkflowhistory/ghworkflowhistory.go | 4 + .../handler/information/information.go | 83 +++++++------- .../terminal/handler/skeleton/skeleton.go | 13 ++- .../terminal/handler/taboptions/taboptions.go | 2 +- 8 files changed, 195 insertions(+), 79 deletions(-) diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/error/error.go index 9f167e4..584f30c 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/error/error.go @@ -2,11 +2,12 @@ package error import ( "fmt" - "strings" - + "github.com/charmbracelet/bubbles/spinner" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" ts "github.com/termkit/gama/internal/terminal/style" + "strings" ) type ModelError struct { @@ -21,6 +22,17 @@ type ModelError struct { // messageType is hold the message type messageType MessageType + + // spinner is hold the spinner + spinner spinner.Model + + updateChan chan UpdateSelf + disableSpinner bool +} + +type UpdateSelf struct { + Message string + InProgress bool } type MessageType string @@ -37,59 +49,129 @@ const ( ) func SetupModelError() ModelError { + s := spinner.New(spinner.WithSpinner(spinner.Dot)) return ModelError{ - err: nil, - errorMessage: "", + spinner: s, + err: nil, + errorMessage: "", + updateChan: make(chan UpdateSelf), + disableSpinner: false, } } +func (m *ModelError) Init() tea.Cmd { + return tea.Batch(m.SelfUpdater()) +} + +func (m *ModelError) Update(msg tea.Msg) (*ModelError, tea.Cmd) { + var cmds []tea.Cmd + var cmd tea.Cmd + + switch msg := msg.(type) { + case spinner.TickMsg: + m.spinner, cmd = m.spinner.Update(msg) + cmds = append(cmds, cmd) + case UpdateSelf: + if msg.InProgress { + m.spinner, cmd = m.spinner.Update(m.spinner.Tick()) + cmds = append(cmds, cmd) + } + } + + cmds = append(cmds, m.SelfUpdater()) + + return m, tea.Batch(cmds...) +} + func (m *ModelError) View() string { var windowStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder()) width := hdltypes.NewTerminalViewport().Width - 4 doc := strings.Builder{} + + var s string + if !m.disableSpinner { + s = m.spinner.View() + } + if m.HaveError() { windowStyle = ts.WindowStyleError.Width(width) - doc.WriteString(windowStyle.Render(m.ViewError())) - return doc.String() + doc.WriteString(windowStyle.Render(m.viewError())) + return lipgloss.JoinHorizontal(lipgloss.Top, doc.String()) } switch m.messageType { case MessageTypeDefault: windowStyle = ts.WindowStyleDefault.Width(width) + s = "" case MessageTypeProgress: windowStyle = ts.WindowStyleProgress.Width(width) case MessageTypeSuccess: windowStyle = ts.WindowStyleSuccess.Width(width) + s = "" default: windowStyle = ts.WindowStyleDefault.Width(width) } - doc.WriteString(windowStyle.Render(m.ViewMessage())) - + doc.WriteString(windowStyle.Render(lipgloss.JoinHorizontal(lipgloss.Top, m.viewMessage(), " ", s))) return doc.String() } +func (m *ModelError) SelfUpdater() tea.Cmd { + return func() tea.Msg { + go func() { + select { + case o := <-m.updateChan: + if o.InProgress { + m.updateChan <- UpdateSelf{Message: o.Message, InProgress: true} + } else { + m.updateChan <- UpdateSelf{Message: o.Message, InProgress: false} + } + } + }() + return <-m.updateChan + } +} + +func (m *ModelError) EnableSpinner() { + m.disableSpinner = false + //m.updateChan <- UpdateSelf{Message: m.message, InProgress: true} +} + +func (m *ModelError) DisableSpinner() { + m.disableSpinner = true + //m.updateChan <- UpdateSelf{Message: m.message, InProgress: false} +} + func (m *ModelError) SetError(err error) { m.err = err } -func (m *ModelError) SetErrorMessage(errorMessage string) { - m.errorMessage = errorMessage +func (m *ModelError) SetErrorMessage(message string) { + m.errorMessage = message + m.updateChan <- UpdateSelf{Message: message, InProgress: true} } func (m *ModelError) SetProgressMessage(message string) { m.messageType = MessageTypeProgress m.message = message + + m.updateChan <- UpdateSelf{Message: message, InProgress: true} } func (m *ModelError) SetSuccessMessage(message string) { + // You should trigger update itself m.messageType = MessageTypeSuccess m.message = message + + m.updateChan <- UpdateSelf{Message: message, InProgress: true} } func (m *ModelError) SetDefaultMessage(message string) { + // You should trigger update itself m.messageType = MessageTypeDefault m.message = message + + m.updateChan <- UpdateSelf{Message: message, InProgress: true} } func (m *ModelError) GetError() error { @@ -122,13 +204,13 @@ func (m *ModelError) HaveError() bool { return m.err != nil } -func (m *ModelError) ViewError() string { +func (m *ModelError) viewError() string { doc := strings.Builder{} doc.WriteString(fmt.Sprintf("Error [%v]: %s", m.err, m.errorMessage)) return doc.String() } -func (m *ModelError) ViewMessage() string { +func (m *ModelError) viewMessage() string { doc := strings.Builder{} doc.WriteString(m.message) return doc.String() diff --git a/internal/terminal/handler/ghrepository/ghrepository.go b/internal/terminal/handler/ghrepository/ghrepository.go index d7cb723..3198ef6 100644 --- a/internal/terminal/handler/ghrepository/ghrepository.go +++ b/internal/terminal/handler/ghrepository/ghrepository.go @@ -20,7 +20,6 @@ import ( "github.com/termkit/gama/pkg/browser" "strconv" "strings" - "time" ) type ModelGithubRepository struct { @@ -48,6 +47,8 @@ type ModelGithubRepository struct { modelTabOptions *taboptions.Options textInput textinput.Model + + updateChan chan updateSelf } func SetupModelGithubRepository(githubUseCase gu.UseCase) *ModelGithubRepository { @@ -121,18 +122,22 @@ func SetupModelGithubRepository(githubUseCase gu.UseCase) *ModelGithubRepository textInput: ti, syncRepositoriesContext: context.Background(), cancelSyncRepositories: func() {}, + updateChan: make(chan updateSelf), } } -type UpdateSpinnerMsg string +type updateSelf struct { +} -func (m *ModelGithubRepository) tickSpinner() tea.Cmd { - t := time.NewTimer(time.Millisecond * 200) +func (m *ModelGithubRepository) SelfUpdater() tea.Cmd { return func() tea.Msg { - select { - case <-t.C: - return UpdateSpinnerMsg("tick") - } + go func() { + select { + case _ = <-m.updateChan: + m.updateChan <- updateSelf{} + } + }() + return <-m.updateChan } } @@ -152,7 +157,8 @@ func (m *ModelGithubRepository) Init() tea.Cmd { m.modelTabOptions.AddOption("Open in browser", openInBrowser) - return tea.Batch(m.modelTabOptions.Init(), m.syncRepositories(m.syncRepositoriesContext), m.tickSpinner()) + go m.syncRepositories(m.syncRepositoriesContext) + return tea.Batch(m.modelTabOptions.Init(), m.modelError.Init(), m.SelfUpdater()) } func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -176,10 +182,13 @@ func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.searchTableGithubRepository.GotoTop() m.searchTableGithubRepository.SetCursor(0) } - case UpdateSpinnerMsg: - return m, m.tickSpinner() + case updateSelf: + cmds = append(cmds, m.SelfUpdater()) } + m.modelError, cmd = m.modelError.Update(msg) + cmds = append(cmds, cmd) + m.textInput, cmd = m.textInput.Update(textInputMsg) cmds = append(cmds, cmd) @@ -225,8 +234,8 @@ func (m *ModelGithubRepository) View() string { return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.viewSearchBar(), m.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) } -func (m *ModelGithubRepository) syncRepositories(ctx context.Context) tea.Cmd { - m.modelError.ResetError() // reset previous errors +func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { + m.modelError.Reset() // reset previous errors m.modelTabOptions.SetStatus(taboptions.OptionWait) m.modelError.SetProgressMessage("Fetching repositories...") @@ -240,18 +249,18 @@ func (m *ModelGithubRepository) syncRepositories(ctx context.Context) tea.Cmd { Sort: domain.SortByUpdated, }) if errors.Is(err, context.Canceled) { - return nil + return } else if err != nil { m.modelError.SetError(err) m.modelError.SetErrorMessage("Repositories cannot be listed") - return nil + return } if len(repositories.Repositories) == 0 { m.modelTabOptions.SetStatus(taboptions.OptionNone) m.modelError.SetDefaultMessage("No repositories found") m.textInput.Blur() - return nil + return } tableRowsGithubRepository := make([]table.Row, 0, len(repositories.Repositories)) @@ -271,8 +280,8 @@ func (m *ModelGithubRepository) syncRepositories(ctx context.Context) tea.Cmd { //m.updateSearchBarSuggestions() m.textInput.Focus() m.modelError.SetSuccessMessage("Repositories fetched") - go m.Update(m) // update model - return nil + + m.updateChan <- updateSelf{} } func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index adbdb12..a2c3bf1 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -51,7 +51,7 @@ type ModelGithubTrigger struct { Help help.Model Viewport *viewport.Model - modelError hdlerror.ModelError + modelError *hdlerror.ModelError textInput textinput.Model tableTrigger table.Model } @@ -82,6 +82,7 @@ func SetupModelGithubTrigger(githubUseCase gu.UseCase) *ModelGithubTrigger { ti.Blur() ti.CharLimit = 72 + modelError := hdlerror.SetupModelError() return &ModelGithubTrigger{ Viewport: hdltypes.NewTerminalViewport(), header: header.NewHeader(), @@ -89,7 +90,7 @@ func SetupModelGithubTrigger(githubUseCase gu.UseCase) *ModelGithubTrigger { Keys: keys, github: githubUseCase, SelectedRepository: hdltypes.NewSelectedRepository(), - modelError: hdlerror.SetupModelError(), + modelError: &modelError, tableTrigger: tableTrigger, textInput: ti, syncWorkflowContext: context.Background(), @@ -98,8 +99,8 @@ func SetupModelGithubTrigger(githubUseCase gu.UseCase) *ModelGithubTrigger { } func (m *ModelGithubTrigger) Init() tea.Cmd { - m.modelError.SetDefaultMessage("No workflow contents found.") - return tea.Batch(textinput.Blink) + //m.modelError.SetDefaultMessage("No workflow contents found.") + return tea.Batch(textinput.Blink, m.modelError.Init()) } func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -178,6 +179,9 @@ func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } + m.modelError, cmd = m.modelError.Update(msg) + cmds = append(cmds, cmd) + m.tableTrigger, cmd = m.tableTrigger.Update(msg) cmds = append(cmds, cmd) diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index d38384b..2c9281f 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -85,10 +85,11 @@ func SetupModelGithubWorkflow(githubUseCase gu.UseCase) *ModelGithubWorkflow { } func (m *ModelGithubWorkflow) Init() tea.Cmd { - return nil + return tea.Batch(m.modelError.Init()) } func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd var cmd tea.Cmd if m.lastRepository != m.SelectedRepository.RepositoryName { @@ -101,11 +102,15 @@ func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { go m.syncTriggerableWorkflows(m.syncTriggerableWorkflowsContext) } + m.modelError, cmd = m.modelError.Update(msg) + cmds = append(cmds, cmd) + m.tableTriggerableWorkflow, cmd = m.tableTriggerableWorkflow.Update(msg) + cmds = append(cmds, cmd) m.handleTableInputs(m.syncTriggerableWorkflowsContext) // update table operations - return m, cmd + return m, tea.Batch(cmds...) } func (m *ModelGithubWorkflow) View() string { diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go index 0f9be6a..9786922 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go @@ -100,6 +100,7 @@ func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { func() tea.Msg { return UpdateWorkflowHistoryMsg{UpdateAfter: time.Second * 1} }, + m.modelError.Init(), ) } @@ -206,6 +207,9 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { }() } + m.modelError, cmd = m.modelError.Update(msg) + cmds = append(cmds, cmd) + m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) cmds = append(cmds, cmd) diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index ec11ec2..12a881e 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/spinner" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -15,7 +14,6 @@ import ( ts "github.com/termkit/gama/internal/terminal/style" pkgversion "github.com/termkit/gama/pkg/version" "strings" - "time" ) type ModelInfo struct { @@ -24,23 +22,25 @@ type ModelInfo struct { // use cases github gu.UseCase - complete bool - // models modelSpirit *spirit.ModelSpirit Help help.Model Viewport *viewport.Model modelError *hdlerror.ModelError - spinner spinner.Model // keymap Keys keyMap + + updateChan chan updateSelf } -const ( - spinnerInterval = 100 * time.Millisecond +type updateSelf struct { + RefreshTerminal bool + Done bool +} +const ( releaseURL = "https://github.com/termkit/gama/releases" applicationName = ` @@ -60,11 +60,6 @@ var ( func SetupModelInfo(githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { modelError := hdlerror.SetupModelError() - //hdlModelHeader := header.NewHeader() - - s := spinner.New() - s.Spinner = spinner.Dot - s.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("120")) return &ModelInfo{ Viewport: types.NewTerminalViewport(), @@ -74,29 +69,16 @@ func SetupModelInfo(githubUseCase gu.UseCase, version pkgversion.Version) *Model Help: help.New(), Keys: keys, modelError: &modelError, - spinner: s, } } -type UpdateSpinnerMsg string - func (m *ModelInfo) Init() tea.Cmd { currentVersion = m.version.CurrentVersion() applicationDescription = fmt.Sprintf("Github Actions Manager (%s)", currentVersion) - go m.testConnection(context.Background()) go m.checkUpdates(context.Background()) - return m.tickSpinner() -} - -func (m *ModelInfo) tickSpinner() tea.Cmd { - t := time.NewTimer(spinnerInterval) - return func() tea.Msg { - select { - case <-t.C: - return UpdateSpinnerMsg("tick") - } - } + go m.testConnection(context.Background()) + return tea.Batch(m.modelError.Init(), m.handleSelfUpdate()) } func (m *ModelInfo) checkUpdates(ctx context.Context) { @@ -111,24 +93,26 @@ func (m *ModelInfo) checkUpdates(ctx context.Context) { if isUpdateAvailable { newVersionAvailableMsg = fmt.Sprintf("New version available: %s\nPlease visit: %s", version, releaseURL) } - - go m.Update(m) } func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmds []tea.Cmd var cmd tea.Cmd - switch msg.(type) { - case UpdateSpinnerMsg: - if m.complete { - return m, nil + switch msg := msg.(type) { + case updateSelf: + //if msg.Done { + // return m, nil + //} + if msg.RefreshTerminal { + m.modelError, cmd = m.modelError.Update(msg) + cmds = append(cmds, cmd) } - - m.modelError.SetProgressMessage("Checking your token " + m.spinner.View()) - m.spinner, cmd = m.spinner.Update(m.spinner.Tick()) - return m, m.tickSpinner() } - return m, cmd + m.modelError, cmd = m.modelError.Update(msg) + cmds = append(cmds, cmd) + + return m, tea.Batch(cmds...) } func (m *ModelInfo) View() string { @@ -153,7 +137,12 @@ func (m *ModelInfo) View() string { } func (m *ModelInfo) testConnection(ctx context.Context) { + m.modelError.EnableSpinner() + m.modelError.SetProgressMessage("Checking your token...") + m.modelSpirit.SetLockTabs(true) + //time.Sleep(3 * time.Second) + _, err := m.github.GetAuthUser(ctx) if err != nil { m.modelError.SetError(err) @@ -165,7 +154,23 @@ func (m *ModelInfo) testConnection(ctx context.Context) { m.modelError.Reset() m.modelError.SetSuccessMessage("Welcome to GAMA!") m.modelSpirit.SetLockTabs(false) - m.complete = true + m.updateChan <- updateSelf{Done: true} +} + +func (m *ModelInfo) handleSelfUpdate() tea.Cmd { + return func() tea.Msg { + go func() { + select { + case o := <-m.updateChan: + if o.Done { + m.updateChan <- updateSelf{Done: true} + } else { + m.updateChan <- updateSelf{RefreshTerminal: true} + } + } + }() + return <-m.updateChan + } } func (m *ModelInfo) ViewStatus() string { diff --git a/internal/terminal/handler/skeleton/skeleton.go b/internal/terminal/handler/skeleton/skeleton.go index 9ebcf74..50eb044 100644 --- a/internal/terminal/handler/skeleton/skeleton.go +++ b/internal/terminal/handler/skeleton/skeleton.go @@ -82,6 +82,11 @@ func (s *Skeleton) Init() tea.Cmd { } func (s *Skeleton) Update(msg tea.Msg) (*Skeleton, tea.Cmd) { + var cmds []tea.Cmd + var cmd tea.Cmd + + s.currentTab = s.header.GetCurrentTab() + switch msg := msg.(type) { case tea.WindowSizeMsg: s.Viewport.Width = msg.Width @@ -108,11 +113,13 @@ func (s *Skeleton) Update(msg tea.Msg) (*Skeleton, tea.Cmd) { } } - var cmd tea.Cmd s.header, cmd = s.header.Update(msg) + cmds = append(cmds, cmd) + s.Pages[s.currentTab], cmd = s.Pages[s.currentTab].Update(msg) - - return s, cmd + cmds = append(cmds, cmd) + + return s, tea.Batch(cmds...) } func (s *Skeleton) View() string { diff --git a/internal/terminal/handler/taboptions/taboptions.go b/internal/terminal/handler/taboptions/taboptions.go index 409cc40..d641d36 100644 --- a/internal/terminal/handler/taboptions/taboptions.go +++ b/internal/terminal/handler/taboptions/taboptions.go @@ -80,7 +80,7 @@ func NewOptions(modelError *hdlerror.ModelError) *Options { func (o *Options) Init() tea.Cmd { return func() tea.Msg { - return tea.KeyMsg{} + return o.modelError.Init() } } From b7eae64a7cb55c8bf46a48e558f10f7cc3a9a7fb Mon Sep 17 00:00:00 2001 From: Engin Date: Sun, 4 Aug 2024 02:09:42 +0300 Subject: [PATCH 08/51] Minor resizing --- internal/terminal/handler/header/header.go | 3 ++- internal/terminal/handler/information/information.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/internal/terminal/handler/header/header.go b/internal/terminal/handler/header/header.go index 90cd12f..99ed205 100644 --- a/internal/terminal/handler/header/header.go +++ b/internal/terminal/handler/header/header.go @@ -156,6 +156,7 @@ func (h *Header) View() string { titleLen := len(titles) var renderedTitles []string + renderedTitles = append(renderedTitles, " ") for i, title := range h.commonHeaders { if h.modelSpirit.GetLockTabs() { if i == 0 { @@ -172,7 +173,7 @@ func (h *Header) View() string { } } - line := strings.Repeat("─", h.Viewport.Width-(titleLen)+10) + line := strings.Repeat("─", h.Viewport.Width-(titleLen)+7) return lipgloss.JoinHorizontal(lipgloss.Center, append(renderedTitles, line)...) } diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index 12a881e..f1b7d1b 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -122,7 +122,7 @@ func (m *ModelInfo) View() string { BorderForeground(lipgloss.Color("39")). Align(lipgloss.Center). Border(lipgloss.RoundedBorder()). - Width(m.Viewport.Width - 3) + Width(m.Viewport.Width - 4).MarginLeft(1) helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) From 71bcbd922476459239b82c5e8878316f5a499640 Mon Sep 17 00:00:00 2001 From: Engin Date: Sun, 4 Aug 2024 02:23:19 +0300 Subject: [PATCH 09/51] Minor enhancements --- internal/terminal/handler/ghtrigger/ghtrigger.go | 12 ++++++------ internal/terminal/handler/ghworkflow/ghworkflow.go | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index a2c3bf1..8cb4f1c 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -346,7 +346,7 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { } func (m *ModelGithubTrigger) View() string { - baseStyle := lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()) + baseStyle := lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).MarginLeft(1) helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) if m.triggerFocused { @@ -366,12 +366,12 @@ func (m *ModelGithubTrigger) View() string { keyWidth := &newTableColumns[2].Width valueWidth := &newTableColumns[4].Width - *valueWidth += widthDiff - 17 + *valueWidth += widthDiff - 14 if *valueWidth%2 == 0 { *keyWidth = *valueWidth / 2 } m.tableTrigger.SetColumns(newTableColumns) - m.tableTrigger.SetHeight(m.Viewport.Height - 17) + m.tableTrigger.SetHeight(m.Viewport.Height - 16) } doc := strings.Builder{} @@ -626,7 +626,7 @@ func (m *ModelGithubTrigger) emptySelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 13) + Width(m.Viewport.Width - 15).MarginLeft(1) // Build the options list doc := strings.Builder{} @@ -639,7 +639,7 @@ func (m *ModelGithubTrigger) inputSelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 13).MarginLeft(2) + Width(m.Viewport.Width - 15).MarginLeft(1) return windowStyle.Render(m.textInput.View()) } @@ -651,7 +651,7 @@ func (m *ModelGithubTrigger) optionSelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 13).MarginLeft(1) + Width(m.Viewport.Width - 15).MarginLeft(1) // Define styles for selected and unselected options selectedOptionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("120")).Padding(0, 1) diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index 2c9281f..339c78e 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -116,7 +116,7 @@ func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *ModelGithubWorkflow) View() string { var style = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")) + BorderForeground(lipgloss.Color("240")).MarginLeft(1) helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) @@ -131,9 +131,9 @@ func (m *ModelGithubWorkflow) View() string { newTableColumns := tableColumnsWorkflow widthDiff := termWidth - tableWidth if widthDiff > 0 { - newTableColumns[1].Width += widthDiff - 11 + newTableColumns[1].Width += widthDiff - 8 m.tableTriggerableWorkflow.SetColumns(newTableColumns) - m.tableTriggerableWorkflow.SetHeight(termHeight - 17) + m.tableTriggerableWorkflow.SetHeight(termHeight - 16) } doc := strings.Builder{} From c0a8dbceaa3da673308715590414511ba64bed7c Mon Sep 17 00:00:00 2001 From: Engin Date: Sun, 4 Aug 2024 20:41:18 +0300 Subject: [PATCH 10/51] Fix blocking calls --- internal/terminal/handler/error/error.go | 38 +++++++++++------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/error/error.go index 584f30c..cfdf593 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/error/error.go @@ -118,17 +118,12 @@ func (m *ModelError) View() string { func (m *ModelError) SelfUpdater() tea.Cmd { return func() tea.Msg { - go func() { - select { - case o := <-m.updateChan: - if o.InProgress { - m.updateChan <- UpdateSelf{Message: o.Message, InProgress: true} - } else { - m.updateChan <- UpdateSelf{Message: o.Message, InProgress: false} - } - } - }() - return <-m.updateChan + select { + case o := <-m.updateChan: + return o + default: + return nil + } } } @@ -148,30 +143,33 @@ func (m *ModelError) SetError(err error) { func (m *ModelError) SetErrorMessage(message string) { m.errorMessage = message - m.updateChan <- UpdateSelf{Message: message, InProgress: true} + go func() { + m.updateChan <- UpdateSelf{Message: message, InProgress: true} + }() } func (m *ModelError) SetProgressMessage(message string) { m.messageType = MessageTypeProgress m.message = message - - m.updateChan <- UpdateSelf{Message: message, InProgress: true} + go func() { + m.updateChan <- UpdateSelf{Message: message, InProgress: true} + }() } func (m *ModelError) SetSuccessMessage(message string) { - // You should trigger update itself m.messageType = MessageTypeSuccess m.message = message - - m.updateChan <- UpdateSelf{Message: message, InProgress: true} + go func() { + m.updateChan <- UpdateSelf{Message: message, InProgress: true} + }() } func (m *ModelError) SetDefaultMessage(message string) { - // You should trigger update itself m.messageType = MessageTypeDefault m.message = message - - m.updateChan <- UpdateSelf{Message: message, InProgress: true} + go func() { + m.updateChan <- UpdateSelf{Message: message, InProgress: true} + }() } func (m *ModelError) GetError() error { From 013baf6d80cc2801154079d50f988013237e85ae Mon Sep 17 00:00:00 2001 From: Engin Date: Sun, 4 Aug 2024 20:41:49 +0300 Subject: [PATCH 11/51] Small code changes --- internal/terminal/handler/ghrepository/ghrepository.go | 4 ++-- internal/terminal/handler/ghtrigger/ghtrigger.go | 2 +- internal/terminal/handler/ghworkflow/ghworkflow.go | 5 +---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/terminal/handler/ghrepository/ghrepository.go b/internal/terminal/handler/ghrepository/ghrepository.go index 3198ef6..a1bb573 100644 --- a/internal/terminal/handler/ghrepository/ghrepository.go +++ b/internal/terminal/handler/ghrepository/ghrepository.go @@ -156,9 +156,9 @@ func (m *ModelGithubRepository) Init() tea.Cmd { } m.modelTabOptions.AddOption("Open in browser", openInBrowser) - + m.modelError.Init() go m.syncRepositories(m.syncRepositoriesContext) - return tea.Batch(m.modelTabOptions.Init(), m.modelError.Init(), m.SelfUpdater()) + return tea.Batch(m.modelTabOptions.Init(), m.SelfUpdater()) } func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index 8cb4f1c..eb9172d 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -99,7 +99,6 @@ func SetupModelGithubTrigger(githubUseCase gu.UseCase) *ModelGithubTrigger { } func (m *ModelGithubTrigger) Init() tea.Cmd { - //m.modelError.SetDefaultMessage("No workflow contents found.") return tea.Batch(textinput.Blink, m.modelError.Init()) } @@ -109,6 +108,7 @@ func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.modelError.SetDefaultMessage("No workflow selected.") return m, nil } + if m.SelectedRepository.WorkflowName != "" && (m.SelectedRepository.WorkflowName != m.selectedWorkflow || m.SelectedRepository.RepositoryName != m.selectedRepositoryName) { m.tableReady = false m.isTriggerable = false diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index 339c78e..ce31a8e 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -145,8 +145,7 @@ func (m *ModelGithubWorkflow) View() string { 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.modelError.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching triggerable workflows...", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) // delete all rows m.tableTriggerableWorkflow.SetRows([]table.Row{}) @@ -184,8 +183,6 @@ func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { m.tableReady = true m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s] Triggerable workflows fetched.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) - - go m.Update(m) // update model } func (m *ModelGithubWorkflow) handleTableInputs(_ context.Context) { From 980d16798e165392854e20e97b105421fc51f53f Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 7 Aug 2024 00:05:37 +0300 Subject: [PATCH 12/51] Clean empty table --- internal/terminal/handler/ghtrigger/ghtrigger.go | 1 + internal/terminal/handler/ghworkflow/ghworkflow.go | 1 + 2 files changed, 2 insertions(+) diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index eb9172d..1790f55 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -106,6 +106,7 @@ func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.SelectedRepository.WorkflowName == "" { m.modelError.Reset() m.modelError.SetDefaultMessage("No workflow selected.") + m.fillTableWithEmptyMessage() return m, nil } diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index ce31a8e..3e85c43 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -163,6 +163,7 @@ func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { } if len(triggerableWorkflows.TriggerableWorkflows) == 0 { + m.SelectedRepository.WorkflowName = "" m.modelError.SetDefaultMessage(fmt.Sprintf("[%s@%s] No triggerable workflow found.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) return } From c89755af3d93b8051c9880616d246ca1f845ccc5 Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 7 Aug 2024 00:07:21 +0300 Subject: [PATCH 13/51] Implement live mode --- config.yaml | 1 + internal/config/config.go | 1 + internal/config/shortcuts.go | 7 ++ .../ghworkflowhistory/ghworkflowhistory.go | 108 +++++++++++++++++- .../handler/ghworkflowhistory/keymap.go | 8 +- 5 files changed, 118 insertions(+), 7 deletions(-) diff --git a/config.yaml b/config.yaml index 65d04b4..c11f0e8 100644 --- a/config.yaml +++ b/config.yaml @@ -6,5 +6,6 @@ keys: switch_tab_left: shift+left quit: ctrl+c refresh: ctrl+r + live_mode: ctrl+l enter: enter tab: tab diff --git a/internal/config/config.go b/internal/config/config.go index d9493a9..a8e745a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -24,6 +24,7 @@ type Shortcuts struct { Quit string `mapstructure:"quit"` Refresh string `mapstructure:"refresh"` Enter string `mapstructure:"enter"` + LiveMode string `mapstructure:"live_mode"` Tab string `mapstructure:"tab"` } diff --git a/internal/config/shortcuts.go b/internal/config/shortcuts.go index 123c728..17286ff 100644 --- a/internal/config/shortcuts.go +++ b/internal/config/shortcuts.go @@ -25,11 +25,16 @@ func fillDefaultShortcuts(cfg *Config) *Config { if tab == "" { tab = defaultKeyMap.Tab } + var liveMode = cfg.Shortcuts.LiveMode + if liveMode == "" { + liveMode = defaultKeyMap.LiveMode + } cfg.Shortcuts = Shortcuts{ SwitchTabRight: switchTabRight, SwitchTabLeft: switchTabLeft, Quit: quit, Refresh: refresh, + LiveMode: liveMode, Enter: enter, Tab: tab, } @@ -44,6 +49,7 @@ type defaultMap struct { Refresh string Enter string Tab string + LiveMode string } var defaultKeyMap = defaultMap{ @@ -53,4 +59,5 @@ var defaultKeyMap = defaultMap{ Refresh: "ctrl+r", Enter: "enter", Tab: "tab", + LiveMode: "ctrl+l", } diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go index 9786922..f6c2869 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go @@ -24,6 +24,8 @@ import ( type ModelGithubWorkflowHistory struct { // current handler's properties tableReady bool + liveMode bool + tableStyle lipgloss.Style updateRound int selectedWorkflowID int64 isTableFocused bool @@ -48,6 +50,8 @@ type ModelGithubWorkflowHistory struct { modelError *hdlerror.ModelError modelTabOptions *taboptions.Options + updateSelfChan chan UpdateWorkflowHistoryMsg + blinkTableChan chan UpdateTableStyleMsg } func SetupModelGithubWorkflowHistory(githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { @@ -72,6 +76,10 @@ func SetupModelGithubWorkflowHistory(githubUseCase gu.UseCase) *ModelGithubWorkf Bold(false) tableWorkflowHistory.SetStyles(s) + var tableStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")).MarginLeft(1) + modelError := hdlerror.SetupModelError() tabOptions := taboptions.NewOptions(&modelError) @@ -86,6 +94,9 @@ func SetupModelGithubWorkflowHistory(githubUseCase gu.UseCase) *ModelGithubWorkf modelTabOptions: tabOptions, syncWorkflowHistoryContext: context.Background(), cancelSyncWorkflowHistory: func() {}, + updateSelfChan: make(chan UpdateWorkflowHistoryMsg), + blinkTableChan: make(chan UpdateTableStyleMsg), + tableStyle: tableStyle, } } @@ -93,14 +104,83 @@ type UpdateWorkflowHistoryMsg struct { UpdateAfter time.Duration } +func (m *ModelGithubWorkflowHistory) SelfUpdater() tea.Cmd { + return func() tea.Msg { + select { + case o := <-m.updateSelfChan: + return o + default: + return nil + } + } +} + +func (m *ModelGithubWorkflowHistory) TableUpdater() tea.Cmd { + return func() tea.Msg { + select { + case o := <-m.blinkTableChan: + return o + default: + return nil + } + } +} + +func (m *ModelGithubWorkflowHistory) ToggleLiveMode() { + // send UpdateWorkflowHistoryMsg to update the workflow history every 5 seconds with ticker + // send only if liveMode is true + go func() { + updateAfter := time.Second * 5 + t := time.NewTicker(updateAfter) + for { + select { + case <-t.C: + if m.liveMode { + m.updateSelfChan <- UpdateWorkflowHistoryMsg{UpdateAfter: time.Nanosecond} + } + } + } + }() +} + +type UpdateTableStyleMsg struct { + Style lipgloss.Style +} + +func (m *ModelGithubWorkflowHistory) BlinkTable() { + // send UpdateTableStyleMsg to update the table style every 1 second with ticker + go func() { + updateAfter := time.Second * 1 + t := time.NewTicker(updateAfter) + + var red bool + for { + select { + case <-t.C: + if m.liveMode { + if red { + m.sendChangeTableColorMessage("112") + } else { + m.sendChangeTableColorMessage("#00aaff") + } + red = !red + } + } + } + }() +} + func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { m.setupOptions() + m.ToggleLiveMode() + m.BlinkTable() return tea.Batch( m.modelTabOptions.Init(), func() tea.Msg { return UpdateWorkflowHistoryMsg{UpdateAfter: time.Second * 1} }, m.modelError.Init(), + m.SelfUpdater(), ) } @@ -198,15 +278,28 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case key.Matches(msg, m.Keys.Refresh): m.tableReady = false go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) + case key.Matches(msg, m.Keys.LiveMode): + m.liveMode = !m.liveMode + if m.liveMode { + m.modelError.SetSuccessMessage("Live mode enabled") + } else { + m.modelError.SetSuccessMessage("Live mode disabled") + m.sendChangeTableColorMessage("240") + } } case UpdateWorkflowHistoryMsg: go func() { time.Sleep(msg.UpdateAfter) m.tableReady = false - m.syncWorkflowHistory(m.syncWorkflowHistoryContext) // TODO : may you use go routine here? + m.syncWorkflowHistory(m.syncWorkflowHistoryContext) }() + case UpdateTableStyleMsg: + m.tableStyle = msg.Style } + cmds = append(cmds, m.SelfUpdater()) + cmds = append(cmds, m.TableUpdater()) + m.modelError, cmd = m.modelError.Update(msg) cmds = append(cmds, cmd) @@ -219,6 +312,12 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } +func (m *ModelGithubWorkflowHistory) sendChangeTableColorMessage(color string) { + go func() { + m.blinkTableChan <- UpdateTableStyleMsg{Style: m.tableStyle.BorderForeground(lipgloss.Color(color))} + }() +} + func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { m.modelError.Reset() m.modelError.SetProgressMessage( @@ -272,9 +371,6 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { } func (m *ModelGithubWorkflowHistory) View() string { - var baseStyle = lipgloss.NewStyle(). - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")).MarginLeft(1) helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) termWidth := m.Viewport.Width @@ -298,10 +394,10 @@ func (m *ModelGithubWorkflowHistory) View() string { m.tableWorkflowHistory.SetColumns(newTableColumns) } - m.tableWorkflowHistory.SetHeight(termHeight - 16) + m.tableWorkflowHistory.SetHeight(termHeight - 17) doc := strings.Builder{} - doc.WriteString(baseStyle.Render(m.tableWorkflowHistory.View())) + doc.WriteString(m.tableStyle.Render(m.tableWorkflowHistory.View())) return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) } diff --git a/internal/terminal/handler/ghworkflowhistory/keymap.go b/internal/terminal/handler/ghworkflowhistory/keymap.go index e0ff75f..f5766c0 100644 --- a/internal/terminal/handler/ghworkflowhistory/keymap.go +++ b/internal/terminal/handler/ghworkflowhistory/keymap.go @@ -12,10 +12,11 @@ type keyMap struct { LaunchTab teakey.Binding Refresh teakey.Binding SwitchTab teakey.Binding + LiveMode teakey.Binding } func (k keyMap) ShortHelp() []teakey.Binding { - return []teakey.Binding{k.SwitchTab, k.Refresh, k.LaunchTab} + return []teakey.Binding{k.SwitchTab, k.Refresh, k.LaunchTab, k.LiveMode} } func (k keyMap) FullHelp() [][]teakey.Binding { @@ -23,6 +24,7 @@ func (k keyMap) FullHelp() [][]teakey.Binding { {k.SwitchTab}, {k.Refresh}, {k.LaunchTab}, + {k.LiveMode}, } } @@ -43,6 +45,10 @@ var keys = func() keyMap { teakey.WithKeys(cfg.Shortcuts.Enter), teakey.WithHelp(cfg.Shortcuts.Enter, "Launch the selected option"), ), + LiveMode: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.LiveMode), + teakey.WithHelp(cfg.Shortcuts.LiveMode, "Toggle live mode"), + ), SwitchTab: teakey.NewBinding( teakey.WithKeys(""), // help-only binding teakey.WithHelp(tabSwitch, "switch tab"), From 8b64ae258a635786b4b5d6badf9f3950ae39c64c Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 7 Aug 2024 00:32:56 +0300 Subject: [PATCH 14/51] Show running workflow's duration --- internal/github/usecase/usecase.go | 12 +++++++----- .../handler/ghworkflowhistory/ghworkflowhistory.go | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/internal/github/usecase/usecase.go b/internal/github/usecase/usecase.go index f2486fb..15c521d 100644 --- a/internal/github/usecase/usecase.go +++ b/internal/github/usecase/usecase.go @@ -213,15 +213,17 @@ func (u useCase) timeToString(t time.Time) string { } func (u useCase) getDuration(startTime time.Time, endTime time.Time, status string) string { - if status != "completed" { - return "running" - } - // Convert UTC times to local timezone localStartTime := startTime.In(time.Local) localEndTime := endTime.In(time.Local) - diff := localEndTime.Sub(localStartTime) + var diff time.Duration + + if status != "completed" { + diff = time.Now().Sub(localEndTime) + } else { + diff = localEndTime.Sub(localStartTime) + } if diff.Seconds() < 60 { return fmt.Sprintf("%ds", int(diff.Seconds())) diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go index f6c2869..84af22b 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go @@ -357,7 +357,7 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { workflowRun.ActionName, workflowRun.TriggeredBy, workflowRun.StartedAt, - workflowRun.Conclusion, + workflowRun.Status, workflowRun.Duration, }) } From 0be055971d0a2eb2f1137a07541029e61b7a1d6f Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 7 Aug 2024 00:47:12 +0300 Subject: [PATCH 15/51] Custom interval support for live mode --- config.yaml | 5 +++++ internal/config/config.go | 13 +++++++++++-- internal/config/settings.go | 11 +++++++++++ .../ghworkflowhistory/ghworkflowhistory.go | 15 +++++++++++---- 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 internal/config/settings.go diff --git a/config.yaml b/config.yaml index c11f0e8..e236939 100644 --- a/config.yaml +++ b/config.yaml @@ -9,3 +9,8 @@ keys: live_mode: ctrl+l enter: enter tab: tab + +settings: + live_mode: + enabled: true # to enable live mode at startup + interval: 15s # interval to refresh the page diff --git a/internal/config/config.go b/internal/config/config.go index a8e745a..80bfd53 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,16 +2,24 @@ package config import ( "fmt" + "github.com/spf13/viper" "os" "path/filepath" "strings" - - "github.com/spf13/viper" + "time" ) type Config struct { Github Github `mapstructure:"github"` Shortcuts Shortcuts `mapstructure:"keys"` + Settings Settings `mapstructure:"settings"` +} + +type Settings struct { + LiveMode struct { + Enabled bool `mapstructure:"enabled"` + Interval time.Duration `mapstructure:"interval"` + } `mapstructure:"live_mode"` } type Github struct { @@ -32,6 +40,7 @@ func LoadConfig() (*Config, error) { var config = new(Config) defer func() { config = fillDefaultShortcuts(config) + config = fillDefaultSettings(config) }() setConfig() diff --git a/internal/config/settings.go b/internal/config/settings.go new file mode 100644 index 0000000..9a2f8a2 --- /dev/null +++ b/internal/config/settings.go @@ -0,0 +1,11 @@ +package config + +import "time" + +func fillDefaultSettings(cfg *Config) *Config { + if cfg.Settings.LiveMode.Interval == time.Duration(0) { + cfg.Settings.LiveMode.Interval = 15 * time.Second + } + + return cfg +} diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go index 84af22b..1ba1b81 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/termkit/gama/internal/config" ts "github.com/termkit/gama/internal/terminal/style" "strings" "time" @@ -25,6 +26,7 @@ type ModelGithubWorkflowHistory struct { // current handler's properties tableReady bool liveMode bool + liveModeInterval time.Duration tableStyle lipgloss.Style updateRound int selectedWorkflowID int64 @@ -55,6 +57,11 @@ type ModelGithubWorkflowHistory struct { } func SetupModelGithubWorkflowHistory(githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { + cfg, err := config.LoadConfig() + if err != nil { + panic(fmt.Sprintf("failed to load config: %v", err)) + } + var tableRowsWorkflowHistory []table.Row tableWorkflowHistory := table.New( @@ -85,6 +92,8 @@ func SetupModelGithubWorkflowHistory(githubUseCase gu.UseCase) *ModelGithubWorkf return &ModelGithubWorkflowHistory{ Viewport: hdltypes.NewTerminalViewport(), + liveMode: cfg.Settings.LiveMode.Enabled, + liveModeInterval: cfg.Settings.LiveMode.Interval, Help: help.New(), Keys: keys, github: githubUseCase, @@ -130,8 +139,7 @@ func (m *ModelGithubWorkflowHistory) ToggleLiveMode() { // send UpdateWorkflowHistoryMsg to update the workflow history every 5 seconds with ticker // send only if liveMode is true go func() { - updateAfter := time.Second * 5 - t := time.NewTicker(updateAfter) + t := time.NewTicker(m.liveModeInterval) for { select { case <-t.C: @@ -276,7 +284,6 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, m.Keys.Refresh): - m.tableReady = false go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) case key.Matches(msg, m.Keys.LiveMode): m.liveMode = !m.liveMode @@ -290,7 +297,6 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case UpdateWorkflowHistoryMsg: go func() { time.Sleep(msg.UpdateAfter) - m.tableReady = false m.syncWorkflowHistory(m.syncWorkflowHistoryContext) }() case UpdateTableStyleMsg: @@ -319,6 +325,7 @@ func (m *ModelGithubWorkflowHistory) sendChangeTableColorMessage(color string) { } func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { + m.tableReady = false m.modelError.Reset() m.modelError.SetProgressMessage( fmt.Sprintf("[%s@%s] Fetching workflow history...", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) From 34334fe5f84749d54421e0f4885aaf6cde439e6b Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 7 Aug 2024 00:49:46 +0300 Subject: [PATCH 16/51] Change trigger pane colors --- internal/terminal/handler/ghtrigger/ghtrigger.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index 1790f55..94df18e 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -353,7 +353,7 @@ func (m *ModelGithubTrigger) View() string { if m.triggerFocused { baseStyle = baseStyle.BorderForeground(lipgloss.Color("240")) } else { - baseStyle = baseStyle.BorderForeground(lipgloss.Color("130")) + baseStyle = baseStyle.BorderForeground(lipgloss.Color("#00aaff")) } var tableWidth int @@ -523,8 +523,8 @@ func (m *ModelGithubTrigger) triggerButton() string { Align(lipgloss.Center) if m.triggerFocused { - button = button.BorderForeground(lipgloss.Color("130")). - Foreground(lipgloss.Color("130")). + button = button.BorderForeground(lipgloss.Color("#00aaff")). + Foreground(lipgloss.Color("#00aaff")). BorderStyle(lipgloss.DoubleBorder()) } From ca9658a8d1eec88eca535f8c927ced578ec680bc Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 7 Aug 2024 01:05:53 +0300 Subject: [PATCH 17/51] remove unused code --- internal/terminal/style/style.go | 28 +++++----------------------- qodana.yaml | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 qodana.yaml diff --git a/internal/terminal/style/style.go b/internal/terminal/style/style.go index 0fee68b..ec8d783 100644 --- a/internal/terminal/style/style.go +++ b/internal/terminal/style/style.go @@ -5,21 +5,17 @@ import ( ) var ( - DocStyle = lipgloss.NewStyle().Padding(1, 2, 1, 2) WindowStyleOrange = lipgloss.NewStyle().BorderForeground(lipgloss.Color("#ffaf00")).Border(lipgloss.RoundedBorder()) WindowStyleRed = lipgloss.NewStyle().BorderForeground(lipgloss.Color("9")).Border(lipgloss.RoundedBorder()) WindowStyleGreen = lipgloss.NewStyle().BorderForeground(lipgloss.Color("10")).Border(lipgloss.RoundedBorder()) WindowStyleGray = lipgloss.NewStyle().BorderForeground(lipgloss.Color("240")).Border(lipgloss.NormalBorder()) WindowStyleWhite = lipgloss.NewStyle().BorderForeground(lipgloss.Color("255")).Border(lipgloss.NormalBorder()) - WindowStyleYellow = lipgloss.NewStyle().BorderForeground(lipgloss.Color("11")).Border(lipgloss.NormalBorder()) - WindowStylePink = lipgloss.NewStyle().BorderForeground(lipgloss.Color("205")).Border(lipgloss.RoundedBorder()) - WindowStyleHelp = WindowStyleGray.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleError = WindowStyleRed.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleProgress = WindowStyleOrange.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleSuccess = WindowStyleGreen.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleDefault = WindowStyleWhite.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleOptionSelector = WindowStylePink.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleHelp = WindowStyleGray.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleError = WindowStyleRed.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleProgress = WindowStyleOrange.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleSuccess = WindowStyleGreen.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleDefault = WindowStyleWhite.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) ) var ( @@ -43,18 +39,4 @@ var ( b.Left = "┤" return lipgloss.NewStyle().BorderStyle(b).Padding(0, 2).BorderForeground(lipgloss.Color("240")).Foreground(lipgloss.Color("240")) }() - - TitleStyleLiveModeOn = func() lipgloss.Style { - b := lipgloss.DoubleBorder() - b.Right = "├" - b.Left = "┤" - return lipgloss.NewStyle().BorderStyle(b).Padding(0, 2).BorderForeground(lipgloss.Color("#aa1010")).Foreground(lipgloss.Color("#ff0000")) - }() - - TitleStyleLiveModeOff = func() lipgloss.Style { - b := lipgloss.DoubleBorder() - b.Right = "├" - b.Left = "┤" - return lipgloss.NewStyle().BorderStyle(b).Padding(0, 2).BorderForeground(lipgloss.Color("#101010")) - }() ) diff --git a/qodana.yaml b/qodana.yaml new file mode 100644 index 0000000..215d808 --- /dev/null +++ b/qodana.yaml @@ -0,0 +1,29 @@ +#-------------------------------------------------------------------------------# +# Qodana analysis is configured by qodana.yaml file # +# https://www.jetbrains.com/help/qodana/qodana-yaml.html # +#-------------------------------------------------------------------------------# +version: "1.0" + +#Specify inspection profile for code analysis +profile: + name: qodana.starter + +#Enable inspections +#include: +# - name: + +#Disable inspections +#exclude: +# - name: +# paths: +# - + +#Execute shell command before Qodana execution (Applied in CI/CD pipeline) +#bootstrap: sh ./prepare-qodana.sh + +#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) +#plugins: +# - id: #(plugin id can be found at https://plugins.jetbrains.com) + +#Specify Qodana linter for analysis (Applied in CI/CD pipeline) +linter: jetbrains/qodana-go:latest From 16afd932d85a8bac74f5a2fa232fe785b6af8ee7 Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 7 Aug 2024 01:07:15 +0300 Subject: [PATCH 18/51] remove unnecessary file --- qodana.yaml | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 qodana.yaml diff --git a/qodana.yaml b/qodana.yaml deleted file mode 100644 index 215d808..0000000 --- a/qodana.yaml +++ /dev/null @@ -1,29 +0,0 @@ -#-------------------------------------------------------------------------------# -# Qodana analysis is configured by qodana.yaml file # -# https://www.jetbrains.com/help/qodana/qodana-yaml.html # -#-------------------------------------------------------------------------------# -version: "1.0" - -#Specify inspection profile for code analysis -profile: - name: qodana.starter - -#Enable inspections -#include: -# - name: - -#Disable inspections -#exclude: -# - name: -# paths: -# - - -#Execute shell command before Qodana execution (Applied in CI/CD pipeline) -#bootstrap: sh ./prepare-qodana.sh - -#Install IDE plugins before Qodana execution (Applied in CI/CD pipeline) -#plugins: -# - id: #(plugin id can be found at https://plugins.jetbrains.com) - -#Specify Qodana linter for analysis (Applied in CI/CD pipeline) -linter: jetbrains/qodana-go:latest From 1c1e23aedc90fbaa9cffe4d784076ef37fdb9578 Mon Sep 17 00:00:00 2001 From: Engin Date: Fri, 9 Aug 2024 00:39:30 +0300 Subject: [PATCH 19/51] first steps of enhanced ui --- .../handler/ghrepository/ghrepository.go | 6 +++--- .../terminal/handler/ghtrigger/ghtrigger.go | 10 ++++----- .../terminal/handler/ghworkflow/ghworkflow.go | 4 ++-- .../ghworkflowhistory/ghworkflowhistory.go | 6 +++--- internal/terminal/handler/header/header.go | 21 ++++++++++++------- .../handler/information/information.go | 10 ++------- .../terminal/handler/skeleton/skeleton.go | 9 +++++++- internal/terminal/style/style.go | 10 ++++----- 8 files changed, 41 insertions(+), 35 deletions(-) diff --git a/internal/terminal/handler/ghrepository/ghrepository.go b/internal/terminal/handler/ghrepository/ghrepository.go index a1bb573..5f8c166 100644 --- a/internal/terminal/handler/ghrepository/ghrepository.go +++ b/internal/terminal/handler/ghrepository/ghrepository.go @@ -223,9 +223,9 @@ func (m *ModelGithubRepository) View() string { newTableColumns := tableColumnsGithubRepository widthDiff := m.Viewport.Width - tableWidth if widthDiff > 0 { - newTableColumns[0].Width += widthDiff - 12 + newTableColumns[0].Width += widthDiff - 14 m.tableGithubRepository.SetColumns(newTableColumns) - m.tableGithubRepository.SetHeight(m.Viewport.Height - 19) + m.tableGithubRepository.SetHeight(m.Viewport.Height - 20) } doc := strings.Builder{} @@ -306,7 +306,7 @@ func (m *ModelGithubRepository) viewSearchBar() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 4).MarginLeft(1) + Width(m.Viewport.Width - 6).MarginLeft(1) // Build the options list doc := strings.Builder{} diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index 94df18e..c9aaf5a 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -367,12 +367,12 @@ func (m *ModelGithubTrigger) View() string { keyWidth := &newTableColumns[2].Width valueWidth := &newTableColumns[4].Width - *valueWidth += widthDiff - 14 + *valueWidth += widthDiff - 16 if *valueWidth%2 == 0 { *keyWidth = *valueWidth / 2 } m.tableTrigger.SetColumns(newTableColumns) - m.tableTrigger.SetHeight(m.Viewport.Height - 16) + m.tableTrigger.SetHeight(m.Viewport.Height - 17) } doc := strings.Builder{} @@ -627,7 +627,7 @@ func (m *ModelGithubTrigger) emptySelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 15).MarginLeft(1) + Width(m.Viewport.Width - 18).MarginLeft(1) // Build the options list doc := strings.Builder{} @@ -640,7 +640,7 @@ func (m *ModelGithubTrigger) inputSelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 15).MarginLeft(1) + Width(m.Viewport.Width - 18).MarginLeft(1) return windowStyle.Render(m.textInput.View()) } @@ -652,7 +652,7 @@ func (m *ModelGithubTrigger) optionSelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 15).MarginLeft(1) + Width(m.Viewport.Width - 18).MarginLeft(1) // Define styles for selected and unselected options selectedOptionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("120")).Padding(0, 1) diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index 3e85c43..d106eda 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -131,9 +131,9 @@ func (m *ModelGithubWorkflow) View() string { newTableColumns := tableColumnsWorkflow widthDiff := termWidth - tableWidth if widthDiff > 0 { - newTableColumns[1].Width += widthDiff - 8 + newTableColumns[1].Width += widthDiff - 10 m.tableTriggerableWorkflow.SetColumns(newTableColumns) - m.tableTriggerableWorkflow.SetHeight(termHeight - 16) + m.tableTriggerableWorkflow.SetHeight(termHeight - 17) } doc := strings.Builder{} diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go index 1ba1b81..61389ee 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go @@ -393,15 +393,15 @@ func (m *ModelGithubWorkflowHistory) View() string { if widthDiff > 0 { if m.updateRound%2 == 0 { - newTableColumns[0].Width += widthDiff - 16 + newTableColumns[0].Width += widthDiff - 18 } else { - newTableColumns[1].Width += widthDiff - 16 + newTableColumns[1].Width += widthDiff - 18 } m.updateRound++ m.tableWorkflowHistory.SetColumns(newTableColumns) } - m.tableWorkflowHistory.SetHeight(termHeight - 17) + m.tableWorkflowHistory.SetHeight(termHeight - 18) doc := strings.Builder{} doc.WriteString(m.tableStyle.Render(m.tableWorkflowHistory.View())) diff --git a/internal/terminal/handler/header/header.go b/internal/terminal/handler/header/header.go index 99ed205..567087a 100644 --- a/internal/terminal/handler/header/header.go +++ b/internal/terminal/handler/header/header.go @@ -147,16 +147,15 @@ func (h *Header) Update(msg tea.Msg) (*Header, tea.Cmd) { // View renders the Header. func (h *Header) View() string { - var titles string - titles += "BBEEE" + var titleLen int for _, title := range h.commonHeaders { - titles += title.rawHeader - titles += "LLL RRR" + titleLen += len(title.rawHeader) + titleLen += title.activeStyle.GetPaddingLeft() + title.activeStyle.GetPaddingRight() + titleLen += 2 // for the border between titles } - titleLen := len(titles) var renderedTitles []string - renderedTitles = append(renderedTitles, " ") + renderedTitles = append(renderedTitles, "") for i, title := range h.commonHeaders { if h.modelSpirit.GetLockTabs() { if i == 0 { @@ -173,7 +172,13 @@ func (h *Header) View() string { } } - line := strings.Repeat("─", h.Viewport.Width-(titleLen)+7) + leftCorner := lipgloss.JoinVertical(lipgloss.Top, "╭", "│") + rightCorner := lipgloss.JoinVertical(lipgloss.Top, "╮", "│") + leftCorner = lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(leftCorner) + rightCorner = lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(rightCorner) - return lipgloss.JoinHorizontal(lipgloss.Center, append(renderedTitles, line)...) + line := strings.Repeat("─", h.Viewport.Width-(titleLen+2)) + line = lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(line) + + return lipgloss.JoinHorizontal(lipgloss.Bottom, leftCorner, lipgloss.JoinHorizontal(lipgloss.Center, append(renderedTitles, line)...), rightCorner) } diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index f1b7d1b..c83a89e 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -118,22 +118,16 @@ func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *ModelInfo) View() string { infoDoc := strings.Builder{} - ws := lipgloss.NewStyle(). - BorderForeground(lipgloss.Color("39")). - Align(lipgloss.Center). - Border(lipgloss.RoundedBorder()). - Width(m.Viewport.Width - 4).MarginLeft(1) - helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) infoDoc.WriteString(lipgloss.JoinVertical(lipgloss.Center, applicationName, applicationDescription, newVersionAvailableMsg)) docHeight := lipgloss.Height(infoDoc.String()) - requiredNewlinesForPadding := m.Viewport.Height - docHeight - 11 + requiredNewlinesForPadding := m.Viewport.Height - docHeight - 10 infoDoc.WriteString(strings.Repeat("\n", max(0, requiredNewlinesForPadding))) - return lipgloss.JoinVertical(lipgloss.Top, ws.Render(infoDoc.String()), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Center, infoDoc.String(), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelInfo) testConnection(ctx context.Context) { diff --git a/internal/terminal/handler/skeleton/skeleton.go b/internal/terminal/handler/skeleton/skeleton.go index 50eb044..ffaaeca 100644 --- a/internal/terminal/handler/skeleton/skeleton.go +++ b/internal/terminal/handler/skeleton/skeleton.go @@ -123,5 +123,12 @@ func (s *Skeleton) Update(msg tea.Msg) (*Skeleton, tea.Cmd) { } func (s *Skeleton) View() string { - return lipgloss.JoinVertical(lipgloss.Top, s.header.View(), s.Pages[s.currentTab].View()) + base := lipgloss.NewStyle(). + BorderForeground(lipgloss.Color("39")). + Align(lipgloss.Center). + Border(lipgloss.RoundedBorder()). + BorderTop(false). + Width(s.Viewport.Width - 2) + + return lipgloss.JoinVertical(lipgloss.Top, s.header.View(), base.Render(s.Pages[s.currentTab].View())) } diff --git a/internal/terminal/style/style.go b/internal/terminal/style/style.go index ec8d783..7bf6f99 100644 --- a/internal/terminal/style/style.go +++ b/internal/terminal/style/style.go @@ -11,11 +11,11 @@ var ( WindowStyleGray = lipgloss.NewStyle().BorderForeground(lipgloss.Color("240")).Border(lipgloss.NormalBorder()) WindowStyleWhite = lipgloss.NewStyle().BorderForeground(lipgloss.Color("255")).Border(lipgloss.NormalBorder()) - WindowStyleHelp = WindowStyleGray.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleError = WindowStyleRed.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleProgress = WindowStyleOrange.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleSuccess = WindowStyleGreen.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleDefault = WindowStyleWhite.Margin(0, 0, 0, 1).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleHelp = WindowStyleGray.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleError = WindowStyleRed.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleProgress = WindowStyleOrange.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleSuccess = WindowStyleGreen.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleDefault = WindowStyleWhite.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) ) var ( From d073ee772d72b02742f9ad242fa1ac9e88626797 Mon Sep 17 00:00:00 2001 From: Engin Date: Sun, 1 Sep 2024 20:27:57 +0300 Subject: [PATCH 20/51] Start to use skeleton package --- go.mod | 33 ++-- go.sum | 91 ++++----- internal/terminal/handler/error/error.go | 8 +- .../handler/ghrepository/ghrepository.go | 20 +- .../terminal/handler/ghtrigger/ghtrigger.go | 29 ++- .../terminal/handler/ghworkflow/ghworkflow.go | 21 +- .../ghworkflowhistory/ghworkflowhistory.go | 17 +- internal/terminal/handler/handler.go | 82 ++------ internal/terminal/handler/header/header.go | 184 ------------------ internal/terminal/handler/header/keymap.go | 32 --- .../handler/information/information.go | 43 ++-- internal/terminal/handler/skeleton/keymap.go | 32 --- .../terminal/handler/skeleton/skeleton.go | 134 ------------- internal/terminal/handler/spirit/spirit.go | 28 --- internal/terminal/handler/types/types.go | 14 -- internal/terminal/style/style.go | 23 --- main.go | 7 +- 17 files changed, 133 insertions(+), 665 deletions(-) delete mode 100644 internal/terminal/handler/header/header.go delete mode 100644 internal/terminal/handler/header/keymap.go delete mode 100644 internal/terminal/handler/skeleton/keymap.go delete mode 100644 internal/terminal/handler/skeleton/skeleton.go delete mode 100644 internal/terminal/handler/spirit/spirit.go diff --git a/go.mod b/go.mod index fbeae81..2313b28 100644 --- a/go.mod +++ b/go.mod @@ -1,24 +1,23 @@ module github.com/termkit/gama -go 1.22.1 +go 1.22.6 require ( - github.com/Masterminds/semver/v3 v3.2.1 - github.com/charmbracelet/bubbles v0.18.0 - github.com/charmbracelet/bubbletea v0.26.4 - github.com/charmbracelet/lipgloss v0.11.0 + github.com/Masterminds/semver/v3 v3.3.0 + github.com/charmbracelet/bubbles v0.19.0 + github.com/charmbracelet/bubbletea v1.1.0 + github.com/charmbracelet/lipgloss v0.13.0 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.9.0 + github.com/termkit/skeleton v0.1.2 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/x/ansi v0.1.2 // indirect - github.com/charmbracelet/x/input v0.1.1 // indirect - github.com/charmbracelet/x/term v0.1.1 // indirect - github.com/charmbracelet/x/windows v0.1.2 // indirect + github.com/charmbracelet/x/ansi v0.2.3 // indirect + github.com/charmbracelet/x/term v0.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -27,28 +26,26 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sahilm/fuzzy v0.1.1 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index 4c16123..dd0dbee 100644 --- a/go.sum +++ b/go.sum @@ -1,33 +1,23 @@ -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= -github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= -github.com/charmbracelet/bubbletea v0.26.3 h1:iXyGvI+FfOWqkB2V07m1DF3xxQijxjY2j8PqiXYqasg= -github.com/charmbracelet/bubbletea v0.26.3/go.mod h1:bpZHfDHTYJC5g+FBK+ptJRCQotRC+Dhh3AoMxa/2+3Q= -github.com/charmbracelet/bubbletea v0.26.4 h1:2gDkkzLZaTjMl/dQBpNVtnvcCxsh/FCkimep7FC9c40= -github.com/charmbracelet/bubbletea v0.26.4/go.mod h1:P+r+RRA5qtI1DOHNFn0otoNwB4rn+zNAzSj/EXz6xU0= -github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= -github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= -github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g= -github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8= -github.com/charmbracelet/x/ansi v0.1.1 h1:CGAduulr6egay/YVbGc8Hsu8deMg1xZ/bkaXTPi1JDk= -github.com/charmbracelet/x/ansi v0.1.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/ansi v0.1.2 h1:6+LR39uG8DE6zAmbu023YlqjJHkYXDF1z36ZwzO4xZY= -github.com/charmbracelet/x/ansi v0.1.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/input v0.1.0 h1:TEsGSfZYQyOtp+STIjyBq6tpRaorH0qpwZUj8DavAhQ= -github.com/charmbracelet/x/input v0.1.0/go.mod h1:ZZwaBxPF7IG8gWWzPUVqHEtWhc1+HXJPNuerJGRGZ28= -github.com/charmbracelet/x/input v0.1.1 h1:YDOJaTUKCqtGnq9PHzx3pkkl4pXDOANUHmhH3DqMtM4= -github.com/charmbracelet/x/input v0.1.1/go.mod h1:jvdTVUnNWj/RD6hjC4FsoB0SeZCJ2ZBkiuFP9zXvZI0= -github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI= -github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw= -github.com/charmbracelet/x/windows v0.1.0 h1:gTaxdvzDM5oMa/I2ZNF7wN78X/atWemG9Wph7Ika2k4= -github.com/charmbracelet/x/windows v0.1.0/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= -github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg= -github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= +github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= +github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= +github.com/charmbracelet/bubbles v0.19.0 h1:gKZkKXPP6GlDk6EcfujDK19PCQqRjaJZQ7QRERx1UF0= +github.com/charmbracelet/bubbles v0.19.0/go.mod h1:WILteEqZ+krG5c3ntGEMeG99nCupcuIk7V0/zOP0tOA= +github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c= +github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4= +github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= +github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= +github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY= +github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q= +github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= +github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= +github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -38,16 +28,14 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -56,17 +44,14 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= -github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= @@ -74,7 +59,6 @@ github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -84,8 +68,6 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= -github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -94,14 +76,13 @@ github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -109,24 +90,22 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4= -golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc h1:O9NuF4s+E/PvMIy+9IUZB9znFwUIXEWSstNjek6VpVg= -golang.org/x/exp v0.0.0-20240531132922-fd00a4e0eefc/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +github.com/termkit/skeleton v0.1.2 h1:8bQD1w0lHI1VF/13JE2Udg65HN3UUcRPJdIvwzLvFq8= +github.com/termkit/skeleton v0.1.2/go.mod h1:guH2iOGt7SkjmvwFytqDVHFv3yh5ZiGv7B0HpuNW4ME= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/error/error.go index cfdf593..37d5328 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/error/error.go @@ -5,12 +5,13 @@ import ( "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - hdltypes "github.com/termkit/gama/internal/terminal/handler/types" ts "github.com/termkit/gama/internal/terminal/style" + "github.com/termkit/skeleton" "strings" ) type ModelError struct { + skeleton *skeleton.Skeleton // err is hold the error err error @@ -48,9 +49,10 @@ const ( MessageTypeSuccess MessageType = "success" ) -func SetupModelError() ModelError { +func SetupModelError(skeleton *skeleton.Skeleton) ModelError { s := spinner.New(spinner.WithSpinner(spinner.Dot)) return ModelError{ + skeleton: skeleton, spinner: s, err: nil, errorMessage: "", @@ -85,7 +87,7 @@ func (m *ModelError) Update(msg tea.Msg) (*ModelError, tea.Cmd) { func (m *ModelError) View() string { var windowStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder()) - width := hdltypes.NewTerminalViewport().Width - 4 + width := m.skeleton.GetTerminalWidth() - 4 doc := strings.Builder{} var s string diff --git a/internal/terminal/handler/ghrepository/ghrepository.go b/internal/terminal/handler/ghrepository/ghrepository.go index 5f8c166..528ec51 100644 --- a/internal/terminal/handler/ghrepository/ghrepository.go +++ b/internal/terminal/handler/ghrepository/ghrepository.go @@ -8,7 +8,6 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textinput" - "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/termkit/gama/internal/github/domain" @@ -18,11 +17,13 @@ import ( hdltypes "github.com/termkit/gama/internal/terminal/handler/types" ts "github.com/termkit/gama/internal/terminal/style" "github.com/termkit/gama/pkg/browser" + "github.com/termkit/skeleton" "strconv" "strings" ) type ModelGithubRepository struct { + skeleton *skeleton.Skeleton // current handler's properties syncRepositoriesContext context.Context cancelSyncRepositories context.CancelFunc @@ -39,7 +40,6 @@ type ModelGithubRepository struct { // models Help help.Model - Viewport *viewport.Model tableGithubRepository table.Model searchTableGithubRepository table.Model modelError *hdlerror.ModelError @@ -51,7 +51,7 @@ type ModelGithubRepository struct { updateChan chan updateSelf } -func SetupModelGithubRepository(githubUseCase gu.UseCase) *ModelGithubRepository { +func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubRepository { var tableRowsGithubRepository []table.Row tableGithubRepository := table.New( @@ -107,11 +107,11 @@ func SetupModelGithubRepository(githubUseCase gu.UseCase) *ModelGithubRepository ti.ShowSuggestions = false // disable suggestions, it will be enabled future. // setup models - modelError := hdlerror.SetupModelError() + modelError := hdlerror.SetupModelError(skeleton) tabOptions := taboptions.NewOptions(&modelError) return &ModelGithubRepository{ - Viewport: hdltypes.NewTerminalViewport(), + skeleton: skeleton, Help: help.New(), Keys: keys, github: githubUseCase, @@ -213,7 +213,7 @@ func (m *ModelGithubRepository) View() string { BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("240")).MarginLeft(1) - helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) + helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) var tableWidth int for _, t := range tableColumnsGithubRepository { @@ -221,11 +221,11 @@ func (m *ModelGithubRepository) View() string { } newTableColumns := tableColumnsGithubRepository - widthDiff := m.Viewport.Width - tableWidth + widthDiff := m.skeleton.GetTerminalWidth() - tableWidth if widthDiff > 0 { newTableColumns[0].Width += widthDiff - 14 m.tableGithubRepository.SetColumns(newTableColumns) - m.tableGithubRepository.SetHeight(m.Viewport.Height - 20) + m.tableGithubRepository.SetHeight(m.skeleton.GetTerminalHeight() - 20) } doc := strings.Builder{} @@ -263,6 +263,8 @@ func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { return } + m.skeleton.UpdateWidgetValue("repositories", fmt.Sprintf("Repository Count: %d", len(repositories.Repositories))) + tableRowsGithubRepository := make([]table.Row, 0, len(repositories.Repositories)) for _, repository := range repositories.Repositories { tableRowsGithubRepository = append(tableRowsGithubRepository, @@ -306,7 +308,7 @@ func (m *ModelGithubRepository) viewSearchBar() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 6).MarginLeft(1) + Width(m.skeleton.GetTerminalWidth() - 6).MarginLeft(1) // Build the options list doc := strings.Builder{} diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index c9aaf5a..d273c18 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -7,22 +7,23 @@ import ( "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textinput" - "github.com/charmbracelet/bubbles/viewport" 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" "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" ts "github.com/termkit/gama/internal/terminal/style" "github.com/termkit/gama/pkg/workflow" + "github.com/termkit/skeleton" "slices" "strings" "time" ) type ModelGithubTrigger struct { + skeleton *skeleton.Skeleton + // current handler's properties syncWorkflowContext context.Context cancelSyncWorkflow context.CancelFunc @@ -47,16 +48,13 @@ type ModelGithubTrigger struct { Keys keyMap // models - header *header.Header - Help help.Model - Viewport *viewport.Model modelError *hdlerror.ModelError textInput textinput.Model tableTrigger table.Model } -func SetupModelGithubTrigger(githubUseCase gu.UseCase) *ModelGithubTrigger { +func SetupModelGithubTrigger(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubTrigger { var tableRowsTrigger []table.Row tableTrigger := table.New( @@ -82,10 +80,9 @@ func SetupModelGithubTrigger(githubUseCase gu.UseCase) *ModelGithubTrigger { ti.Blur() ti.CharLimit = 72 - modelError := hdlerror.SetupModelError() + modelError := hdlerror.SetupModelError(skeleton) return &ModelGithubTrigger{ - Viewport: hdltypes.NewTerminalViewport(), - header: header.NewHeader(), + skeleton: skeleton, Help: help.New(), Keys: keys, github: githubUseCase, @@ -348,7 +345,7 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { func (m *ModelGithubTrigger) View() string { baseStyle := lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).MarginLeft(1) - helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) + helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) if m.triggerFocused { baseStyle = baseStyle.BorderForeground(lipgloss.Color("240")) @@ -362,7 +359,7 @@ func (m *ModelGithubTrigger) View() string { } newTableColumns := tableColumnsTrigger - widthDiff := m.Viewport.Width - tableWidth + widthDiff := m.skeleton.GetTerminalWidth() - tableWidth if widthDiff > 0 { keyWidth := &newTableColumns[2].Width valueWidth := &newTableColumns[4].Width @@ -372,7 +369,7 @@ func (m *ModelGithubTrigger) View() string { *keyWidth = *valueWidth / 2 } m.tableTrigger.SetColumns(newTableColumns) - m.tableTrigger.SetHeight(m.Viewport.Height - 17) + m.tableTrigger.SetHeight(m.skeleton.GetTerminalHeight() - 17) } doc := strings.Builder{} @@ -619,7 +616,7 @@ func (m *ModelGithubTrigger) triggerWorkflow() { m.optionValues = nil // reset option values m.selectedRepositoryName = "" // reset selected repository name - m.header.SetCurrentTab(2) // switch tab to workflow history + m.skeleton.SetActivePage("history") // switch tab to workflow history } func (m *ModelGithubTrigger) emptySelector() string { @@ -627,7 +624,7 @@ func (m *ModelGithubTrigger) emptySelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 18).MarginLeft(1) + Width(m.skeleton.GetTerminalWidth() - 18).MarginLeft(1) // Build the options list doc := strings.Builder{} @@ -640,7 +637,7 @@ func (m *ModelGithubTrigger) inputSelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 18).MarginLeft(1) + Width(m.skeleton.GetTerminalWidth() - 18).MarginLeft(1) return windowStyle.Render(m.textInput.View()) } @@ -652,7 +649,7 @@ func (m *ModelGithubTrigger) optionSelector() string { windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). Padding(0, 1). - Width(m.Viewport.Width - 18).MarginLeft(1) + Width(m.skeleton.GetTerminalWidth() - 18).MarginLeft(1) // Define styles for selected and unselected options selectedOptionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("120")).Padding(0, 1) diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index d106eda..9913642 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -5,23 +5,22 @@ import ( "errors" "fmt" ts "github.com/termkit/gama/internal/terminal/style" + "github.com/termkit/skeleton" "sort" "strings" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/table" hdlerror "github.com/termkit/gama/internal/terminal/handler/error" - "github.com/termkit/gama/internal/terminal/handler/ghtrigger" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" - "github.com/charmbracelet/bubbles/list" - "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" gu "github.com/termkit/gama/internal/github/usecase" ) type ModelGithubWorkflow struct { + skeleton *skeleton.Skeleton // current handler's properties syncTriggerableWorkflowsContext context.Context cancelSyncTriggerableWorkflows context.CancelFunc @@ -39,15 +38,11 @@ type ModelGithubWorkflow struct { // models Help help.Model - Viewport *viewport.Model - list list.Model tableTriggerableWorkflow table.Model modelError *hdlerror.ModelError - - modelGithubTrigger *ghtrigger.ModelGithubTrigger } -func SetupModelGithubWorkflow(githubUseCase gu.UseCase) *ModelGithubWorkflow { +func SetupModelGithubWorkflow(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflow { var tableRowsTriggerableWorkflow []table.Row tableTriggerableWorkflow := table.New( @@ -69,10 +64,10 @@ func SetupModelGithubWorkflow(githubUseCase gu.UseCase) *ModelGithubWorkflow { Bold(false) tableTriggerableWorkflow.SetStyles(s) - modelError := hdlerror.SetupModelError() + modelError := hdlerror.SetupModelError(skeleton) return &ModelGithubWorkflow{ - Viewport: hdltypes.NewTerminalViewport(), + skeleton: skeleton, Help: help.New(), Keys: keys, github: githubUseCase, @@ -118,10 +113,10 @@ func (m *ModelGithubWorkflow) View() string { BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("240")).MarginLeft(1) - helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) + helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) - termWidth := m.Viewport.Width - termHeight := m.Viewport.Height + termWidth := m.skeleton.GetTerminalWidth() + termHeight := m.skeleton.GetTerminalHeight() var tableWidth int for _, t := range tableColumnsWorkflow { diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go index 61389ee..aa951cb 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go @@ -6,13 +6,13 @@ import ( "fmt" "github.com/termkit/gama/internal/config" ts "github.com/termkit/gama/internal/terminal/style" + "github.com/termkit/skeleton" "strings" "time" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/table" - "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" gu "github.com/termkit/gama/internal/github/usecase" @@ -23,6 +23,7 @@ import ( ) type ModelGithubWorkflowHistory struct { + skeleton *skeleton.Skeleton // current handler's properties tableReady bool liveMode bool @@ -30,7 +31,6 @@ type ModelGithubWorkflowHistory struct { tableStyle lipgloss.Style updateRound int selectedWorkflowID int64 - isTableFocused bool lastRepository string syncWorkflowHistoryContext context.Context cancelSyncWorkflowHistory context.CancelFunc @@ -47,7 +47,6 @@ type ModelGithubWorkflowHistory struct { // models Help help.Model - Viewport *viewport.Model tableWorkflowHistory table.Model modelError *hdlerror.ModelError @@ -56,7 +55,7 @@ type ModelGithubWorkflowHistory struct { blinkTableChan chan UpdateTableStyleMsg } -func SetupModelGithubWorkflowHistory(githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { +func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { cfg, err := config.LoadConfig() if err != nil { panic(fmt.Sprintf("failed to load config: %v", err)) @@ -87,11 +86,11 @@ func SetupModelGithubWorkflowHistory(githubUseCase gu.UseCase) *ModelGithubWorkf BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("240")).MarginLeft(1) - modelError := hdlerror.SetupModelError() + modelError := hdlerror.SetupModelError(skeleton) tabOptions := taboptions.NewOptions(&modelError) return &ModelGithubWorkflowHistory{ - Viewport: hdltypes.NewTerminalViewport(), + skeleton: skeleton, liveMode: cfg.Settings.LiveMode.Enabled, liveModeInterval: cfg.Settings.LiveMode.Interval, Help: help.New(), @@ -378,10 +377,10 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { } func (m *ModelGithubWorkflowHistory) View() string { - helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) + helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) - termWidth := m.Viewport.Width - termHeight := m.Viewport.Height + termWidth := m.skeleton.GetTerminalWidth() + termHeight := m.skeleton.GetTerminalHeight() var tableWidth int for _, t := range tableColumnsWorkflowHistory { diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index 2f6ba76..5d607e6 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -1,8 +1,6 @@ package handler import ( - "fmt" - "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" gu "github.com/termkit/gama/internal/github/usecase" hdlgithubrepo "github.com/termkit/gama/internal/terminal/handler/ghrepository" @@ -10,80 +8,30 @@ import ( hdlWorkflow "github.com/termkit/gama/internal/terminal/handler/ghworkflow" hdlworkflowhistory "github.com/termkit/gama/internal/terminal/handler/ghworkflowhistory" hdlinfo "github.com/termkit/gama/internal/terminal/handler/information" - hdlskeleton "github.com/termkit/gama/internal/terminal/handler/skeleton" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" - ts "github.com/termkit/gama/internal/terminal/style" pkgversion "github.com/termkit/gama/pkg/version" + "github.com/termkit/skeleton" ) -type model struct { - // models - viewport *viewport.Model - modelSkeleton *hdlskeleton.Skeleton - - // keymap - keys keyMap -} - func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Model { - skeleton := hdlskeleton.NewSkeleton() - - skeleton.AddPage(hdlskeleton.Title{Title: "Info", Style: hdlskeleton.TitleStyle{ - Active: ts.TitleStyleActive, - Inactive: ts.TitleStyleInactive, - }}, hdlinfo.SetupModelInfo(githubUseCase, version)) + s := skeleton.NewSkeleton() - skeleton.AddPage(hdlskeleton.Title{Title: "Repository", Style: hdlskeleton.TitleStyle{ - Active: ts.TitleStyleActive, - Inactive: ts.TitleStyleInactive, - }}, hdlgithubrepo.SetupModelGithubRepository(githubUseCase)) + s.AddPage("info", "Info", hdlinfo.SetupModelInfo(s, githubUseCase, version)). + AddPage("repository", "Repository", hdlgithubrepo.SetupModelGithubRepository(s, githubUseCase)). + AddPage("history", "Workflow History", hdlworkflowhistory.SetupModelGithubWorkflowHistory(s, githubUseCase)). + AddPage("workflow", "Workflow", hdlWorkflow.SetupModelGithubWorkflow(s, githubUseCase)). + AddPage("trigger", "Trigger", hdltrigger.SetupModelGithubTrigger(s, githubUseCase)) - skeleton.AddPage(hdlskeleton.Title{Title: "Workflow History", Style: hdlskeleton.TitleStyle{ - Active: ts.TitleStyleActive, - Inactive: ts.TitleStyleInactive, - }}, hdlworkflowhistory.SetupModelGithubWorkflowHistory(githubUseCase)) + s.SetBorderColor("49").SetActiveTabBorderColor("#ff0055") - skeleton.AddPage(hdlskeleton.Title{Title: "Workflow", Style: hdlskeleton.TitleStyle{ - Active: ts.TitleStyleActive, - Inactive: ts.TitleStyleInactive, - }}, hdlWorkflow.SetupModelGithubWorkflow(githubUseCase)) + s.AddWidget("version", "development mode") - skeleton.AddPage(hdlskeleton.Title{Title: "Trigger", Style: hdlskeleton.TitleStyle{ - Active: ts.TitleStyleActive, - Inactive: ts.TitleStyleInactive, - }}, hdltrigger.SetupModelGithubTrigger(githubUseCase)) - - m := model{ - viewport: hdltypes.NewTerminalViewport(), - modelSkeleton: skeleton, - keys: keys, - } - - return &m -} - -func (m *model) Init() tea.Cmd { - return tea.Batch( - tea.EnterAltScreen, - tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), - m.modelSkeleton.Init(), - ) -} - -func (m *model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmds []tea.Cmd - - var cmd tea.Cmd - m.modelSkeleton, cmd = m.modelSkeleton.Update(msg) - cmds = append(cmds, cmd) - - return m, tea.Batch(cmds...) -} + s.SetTerminalViewportWidth(hdltypes.MinTerminalWidth) + s.SetTerminalViewportHeight(hdltypes.MinTerminalHeight) -func (m *model) View() string { - 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) - } + s.KeyMap.SetKeyNextTab(keys.SwitchTabRight) + s.KeyMap.SetKeyPrevTab(keys.SwitchTabLeft) + s.KeyMap.SetKeyQuit(keys.Quit) - return m.modelSkeleton.View() + return s } diff --git a/internal/terminal/handler/header/header.go b/internal/terminal/handler/header/header.go deleted file mode 100644 index 567087a..0000000 --- a/internal/terminal/handler/header/header.go +++ /dev/null @@ -1,184 +0,0 @@ -package header - -import ( - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/termkit/gama/internal/terminal/handler/spirit" - "github.com/termkit/gama/internal/terminal/handler/types" - ts "github.com/termkit/gama/internal/terminal/style" - "strings" - "sync" - "time" -) - -// Header is a helper for rendering the Header of the terminal. -type Header struct { - Viewport *viewport.Model - - keys keyMap - - currentTab int - - modelSpirit *spirit.ModelSpirit - - commonHeaders []commonHeader - specialHeader specialHeader - specialHeaderInterval time.Duration - currentSpecialStyle int -} - -type commonHeader struct { - header string - rawHeader string - - inactiveStyle lipgloss.Style - activeStyle lipgloss.Style -} - -type specialHeader struct { - header string - rawHeader string - - styles []lipgloss.Style -} - -// Define sync.Once and NewHeader should return same instance -var ( - once sync.Once - h *Header -) - -// NewHeader returns a new Header. -func NewHeader() *Header { - once.Do(func() { - s := spirit.NewSpirit() - s.SetLockTabs(false) - h = &Header{ - modelSpirit: s, - specialHeaderInterval: time.Millisecond * 100, - Viewport: types.NewTerminalViewport(), - currentTab: 0, - keys: keys, - } - }) - return h -} - -func (h *Header) SetCurrentTab(tab int) { - h.currentTab = tab -} - -func (h *Header) GetCurrentTab() int { - return h.currentTab -} - -func (h *Header) AddCommonHeader(header string, activeStyle, inactiveStyle lipgloss.Style) { - h.commonHeaders = append(h.commonHeaders, commonHeader{ - header: header, - rawHeader: header, - inactiveStyle: inactiveStyle, - activeStyle: activeStyle, - }) -} - -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 { - Msg string - UpdatingComponent string -} - -func (h *Header) Init() tea.Cmd { - return h.tick() -} - -func (h *Header) tick() tea.Cmd { - t := time.NewTimer(h.specialHeaderInterval) - return func() tea.Msg { - select { - case <-t.C: - return UpdateMsg{ - Msg: "tick", - UpdatingComponent: "header", - } - } - } -} - -func (h *Header) Update(msg tea.Msg) (*Header, tea.Cmd) { - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, h.keys.SwitchTabLeft): - if !h.modelSpirit.GetLockTabs() { - h.currentTab = max(h.currentTab-1, 0) - } - case key.Matches(msg, h.keys.SwitchTabRight): - if !h.modelSpirit.GetLockTabs() { - h.currentTab = min(h.currentTab+1, len(h.commonHeaders)-1) - } - } - case UpdateMsg: - if msg.UpdatingComponent == "header" { - if h.currentSpecialStyle >= len(h.specialHeader.styles)-1 { - h.currentSpecialStyle = 0 - } else { - h.currentSpecialStyle++ - } - } - - return h, h.Init() - } - - return h, nil -} - -// View renders the Header. -func (h *Header) View() string { - var titleLen int - for _, title := range h.commonHeaders { - titleLen += len(title.rawHeader) - titleLen += title.activeStyle.GetPaddingLeft() + title.activeStyle.GetPaddingRight() - titleLen += 2 // for the border between titles - } - - var renderedTitles []string - renderedTitles = append(renderedTitles, "") - for i, title := range h.commonHeaders { - if h.modelSpirit.GetLockTabs() { - if i == 0 { - renderedTitles = append(renderedTitles, title.activeStyle.Render(title.header)) - } else { - renderedTitles = append(renderedTitles, ts.TitleStyleDisabled.Render(title.header)) - } - } else { - if i == h.currentTab { - renderedTitles = append(renderedTitles, title.activeStyle.Render(title.header)) - } else { - renderedTitles = append(renderedTitles, title.inactiveStyle.Render(title.header)) - } - } - } - - leftCorner := lipgloss.JoinVertical(lipgloss.Top, "╭", "│") - rightCorner := lipgloss.JoinVertical(lipgloss.Top, "╮", "│") - leftCorner = lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(leftCorner) - rightCorner = lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(rightCorner) - - line := strings.Repeat("─", h.Viewport.Width-(titleLen+2)) - line = lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Render(line) - - return lipgloss.JoinHorizontal(lipgloss.Bottom, leftCorner, lipgloss.JoinHorizontal(lipgloss.Center, append(renderedTitles, line)...), rightCorner) -} diff --git a/internal/terminal/handler/header/keymap.go b/internal/terminal/handler/header/keymap.go deleted file mode 100644 index 5e1feba..0000000 --- a/internal/terminal/handler/header/keymap.go +++ /dev/null @@ -1,32 +0,0 @@ -package header - -import ( - "fmt" - "github.com/termkit/gama/internal/config" - - teakey "github.com/charmbracelet/bubbles/key" -) - -type keyMap struct { - SwitchTabRight teakey.Binding - SwitchTabLeft teakey.Binding - Quit teakey.Binding -} - -var keys = func() keyMap { - cfg, err := config.LoadConfig() - if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) - } - return keyMap{ - SwitchTabRight: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.SwitchTabRight), - ), - SwitchTabLeft: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.SwitchTabLeft), - ), - Quit: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Quit), - ), - } -}() diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index c83a89e..783d8cc 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -4,29 +4,25 @@ import ( "context" "fmt" "github.com/charmbracelet/bubbles/help" - "github.com/charmbracelet/bubbles/viewport" 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" - "github.com/termkit/gama/internal/terminal/handler/spirit" - "github.com/termkit/gama/internal/terminal/handler/types" ts "github.com/termkit/gama/internal/terminal/style" pkgversion "github.com/termkit/gama/pkg/version" + "github.com/termkit/skeleton" "strings" ) type ModelInfo struct { - version pkgversion.Version + skeleton *skeleton.Skeleton + version pkgversion.Version // use cases github gu.UseCase // models - modelSpirit *spirit.ModelSpirit - Help help.Model - Viewport *viewport.Model modelError *hdlerror.ModelError // keymap @@ -58,17 +54,16 @@ var ( applicationDescription string ) -func SetupModelInfo(githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { - modelError := hdlerror.SetupModelError() +func SetupModelInfo(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { + modelError := hdlerror.SetupModelError(skeleton) return &ModelInfo{ - Viewport: types.NewTerminalViewport(), - modelSpirit: spirit.NewSpirit(), - github: githubUseCase, - version: version, - Help: help.New(), - Keys: keys, - modelError: &modelError, + skeleton: skeleton, + github: githubUseCase, + version: version, + Help: help.New(), + Keys: keys, + modelError: &modelError, } } @@ -78,7 +73,9 @@ func (m *ModelInfo) Init() tea.Cmd { go m.checkUpdates(context.Background()) go m.testConnection(context.Background()) - return tea.Batch(m.modelError.Init(), m.handleSelfUpdate()) + + return tea.Batch(tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), + m.modelError.Init(), m.handleSelfUpdate()) } func (m *ModelInfo) checkUpdates(ctx context.Context) { @@ -118,12 +115,12 @@ func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *ModelInfo) View() string { infoDoc := strings.Builder{} - helpWindowStyle := ts.WindowStyleHelp.Width(m.Viewport.Width - 4) + helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) infoDoc.WriteString(lipgloss.JoinVertical(lipgloss.Center, applicationName, applicationDescription, newVersionAvailableMsg)) docHeight := lipgloss.Height(infoDoc.String()) - requiredNewlinesForPadding := m.Viewport.Height - docHeight - 10 + requiredNewlinesForPadding := m.skeleton.GetTerminalHeight() - docHeight - 12 infoDoc.WriteString(strings.Repeat("\n", max(0, requiredNewlinesForPadding))) @@ -133,21 +130,19 @@ func (m *ModelInfo) View() string { func (m *ModelInfo) testConnection(ctx context.Context) { m.modelError.EnableSpinner() m.modelError.SetProgressMessage("Checking your token...") - m.modelSpirit.SetLockTabs(true) - - //time.Sleep(3 * time.Second) + m.skeleton.LockTabs() _, err := m.github.GetAuthUser(ctx) if err != nil { m.modelError.SetError(err) m.modelError.SetErrorMessage("failed to test connection, please check your token&permission") - m.modelSpirit.SetLockTabs(true) + m.skeleton.LockTabs() return } m.modelError.Reset() m.modelError.SetSuccessMessage("Welcome to GAMA!") - m.modelSpirit.SetLockTabs(false) + m.skeleton.UnlockTabs() m.updateChan <- updateSelf{Done: true} } diff --git a/internal/terminal/handler/skeleton/keymap.go b/internal/terminal/handler/skeleton/keymap.go deleted file mode 100644 index 2443d47..0000000 --- a/internal/terminal/handler/skeleton/keymap.go +++ /dev/null @@ -1,32 +0,0 @@ -package skeleton - -import ( - "fmt" - "github.com/termkit/gama/internal/config" - - teakey "github.com/charmbracelet/bubbles/key" -) - -type keyMap struct { - SwitchTabRight teakey.Binding - SwitchTabLeft teakey.Binding - Quit teakey.Binding -} - -var keys = func() keyMap { - cfg, err := config.LoadConfig() - if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) - } - return keyMap{ - SwitchTabRight: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.SwitchTabRight), - ), - SwitchTabLeft: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.SwitchTabLeft), - ), - Quit: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Quit), - ), - } -}() diff --git a/internal/terminal/handler/skeleton/skeleton.go b/internal/terminal/handler/skeleton/skeleton.go deleted file mode 100644 index ffaaeca..0000000 --- a/internal/terminal/handler/skeleton/skeleton.go +++ /dev/null @@ -1,134 +0,0 @@ -package skeleton - -import ( - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/termkit/gama/internal/terminal/handler/header" - "github.com/termkit/gama/internal/terminal/handler/spirit" - "github.com/termkit/gama/internal/terminal/handler/types" - "sync" -) - -// Skeleton is a helper for rendering the Skeleton of the terminal. -type Skeleton struct { - Viewport *viewport.Model - - header *header.Header - modelSpirit *spirit.ModelSpirit - lockTabs bool - - keys keyMap - - currentTab int - Pages []tea.Model -} - -func (s *Skeleton) AddPage(title Title, page tea.Model) { - s.header.AddCommonHeader(title.Title, title.Style.Active, title.Style.Inactive) - s.Pages = append(s.Pages, page) -} - -type Title struct { - Title string - Style TitleStyle -} - -type TitleStyle struct { - Active lipgloss.Style - Inactive lipgloss.Style -} - -var ( - once sync.Once - s *Skeleton -) - -// NewSkeleton returns a new Skeleton. -func NewSkeleton() *Skeleton { - once.Do(func() { - s = &Skeleton{ - Viewport: types.NewTerminalViewport(), - header: header.NewHeader(), - modelSpirit: spirit.NewSpirit(), - keys: keys, - } - }) - return s -} - -type SwitchTab struct { - Tab int -} - -func (s *Skeleton) SetCurrentTab(tab int) { - s.currentTab = tab -} - -func (s *Skeleton) Init() tea.Cmd { - self := func() tea.Msg { - return SwitchTab{} - } - - inits := make([]tea.Cmd, len(s.Pages)+1) // +1 for self - for i := range s.Pages { - inits[i] = s.Pages[i].Init() - } - - inits[len(s.Pages)] = self - - return tea.Batch(inits...) -} - -func (s *Skeleton) Update(msg tea.Msg) (*Skeleton, tea.Cmd) { - var cmds []tea.Cmd - var cmd tea.Cmd - - s.currentTab = s.header.GetCurrentTab() - - switch msg := msg.(type) { - case tea.WindowSizeMsg: - s.Viewport.Width = msg.Width - s.Viewport.Height = msg.Height - //case SwitchTab: - // s.SetCurrentTab(msg.Tab) - // s.header.SetCurrentTab(msg.Tab) - // - // var cmd tea.Cmd - // s.Pages[s.currentTab], cmd = s.Pages[msg.Tab].Update(msg) - // return s, cmd - case tea.KeyMsg: - switch { - case key.Matches(msg, s.keys.Quit): - return s, tea.Quit - case key.Matches(msg, s.keys.SwitchTabLeft): - if !s.modelSpirit.GetLockTabs() { - s.currentTab = max(s.currentTab-1, 0) - } - case key.Matches(msg, s.keys.SwitchTabRight): - if !s.modelSpirit.GetLockTabs() { - s.currentTab = min(s.currentTab+1, len(s.Pages)-1) - } - } - } - - s.header, cmd = s.header.Update(msg) - cmds = append(cmds, cmd) - - s.Pages[s.currentTab], cmd = s.Pages[s.currentTab].Update(msg) - cmds = append(cmds, cmd) - - return s, tea.Batch(cmds...) -} - -func (s *Skeleton) View() string { - base := lipgloss.NewStyle(). - BorderForeground(lipgloss.Color("39")). - Align(lipgloss.Center). - Border(lipgloss.RoundedBorder()). - BorderTop(false). - Width(s.Viewport.Width - 2) - - return lipgloss.JoinVertical(lipgloss.Top, s.header.View(), base.Render(s.Pages[s.currentTab].View())) -} diff --git a/internal/terminal/handler/spirit/spirit.go b/internal/terminal/handler/spirit/spirit.go deleted file mode 100644 index b635325..0000000 --- a/internal/terminal/handler/spirit/spirit.go +++ /dev/null @@ -1,28 +0,0 @@ -package spirit - -import "sync" - -type ModelSpirit struct { - lockTabs bool -} - -var ( - once sync.Once - s *ModelSpirit -) - -// NewSpirit returns a new Spirit. -func NewSpirit() *ModelSpirit { - once.Do(func() { - s = &ModelSpirit{} - }) - return s -} - -func (s *ModelSpirit) SetLockTabs(lock bool) { - s.lockTabs = lock -} - -func (s *ModelSpirit) GetLockTabs() bool { - return s.lockTabs -} diff --git a/internal/terminal/handler/types/types.go b/internal/terminal/handler/types/types.go index 8f42c6f..b4df73f 100644 --- a/internal/terminal/handler/types/types.go +++ b/internal/terminal/handler/types/types.go @@ -1,8 +1,6 @@ package types import ( - "github.com/charmbracelet/bubbles/viewport" - "sync" ) @@ -30,15 +28,3 @@ 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/internal/terminal/style/style.go b/internal/terminal/style/style.go index 7bf6f99..cc54ecf 100644 --- a/internal/terminal/style/style.go +++ b/internal/terminal/style/style.go @@ -17,26 +17,3 @@ var ( WindowStyleSuccess = WindowStyleGreen.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) WindowStyleDefault = WindowStyleWhite.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) ) - -var ( - TitleStyleActive = func() lipgloss.Style { - b := lipgloss.DoubleBorder() - b.Right = "├" - b.Left = "┤" - return lipgloss.NewStyle().BorderStyle(b).Padding(0, 2).BorderForeground(lipgloss.Color("205")) - }() - - TitleStyleInactive = func() lipgloss.Style { - b := lipgloss.RoundedBorder() - b.Right = "├" - b.Left = "┤" - return lipgloss.NewStyle().BorderStyle(b).Padding(0, 2).BorderForeground(lipgloss.Color("255")) - }() - - TitleStyleDisabled = func() lipgloss.Style { - b := lipgloss.RoundedBorder() - b.Right = "├" - b.Left = "┤" - return lipgloss.NewStyle().BorderStyle(b).Padding(0, 2).BorderForeground(lipgloss.Color("240")).Foreground(lipgloss.Color("240")) - }() -) diff --git a/main.go b/main.go index 8b7a27a..6d7e1d5 100644 --- a/main.go +++ b/main.go @@ -2,14 +2,15 @@ package main import ( "fmt" - "github.com/termkit/gama/internal/config" - pkgversion "github.com/termkit/gama/pkg/version" "os" - tea "github.com/charmbracelet/bubbletea" + "github.com/termkit/gama/internal/config" gr "github.com/termkit/gama/internal/github/repository" gu "github.com/termkit/gama/internal/github/usecase" th "github.com/termkit/gama/internal/terminal/handler" + pkgversion "github.com/termkit/gama/pkg/version" + + tea "github.com/charmbracelet/bubbletea" ) const ( From e54e3e5049295234469c7e4c3c188860b0461aa2 Mon Sep 17 00:00:00 2001 From: Engin Date: Mon, 2 Sep 2024 22:37:35 +0300 Subject: [PATCH 21/51] Enhance UI --- .../handler/ghrepository/ghrepository.go | 3 +- .../terminal/handler/ghtrigger/ghtrigger.go | 9 ++- .../terminal/handler/ghworkflow/ghworkflow.go | 4 +- .../ghworkflowhistory/ghworkflowhistory.go | 55 +------------------ internal/terminal/handler/handler.go | 9 ++- .../handler/information/information.go | 6 ++ 6 files changed, 26 insertions(+), 60 deletions(-) diff --git a/internal/terminal/handler/ghrepository/ghrepository.go b/internal/terminal/handler/ghrepository/ghrepository.go index 528ec51..c4af5f9 100644 --- a/internal/terminal/handler/ghrepository/ghrepository.go +++ b/internal/terminal/handler/ghrepository/ghrepository.go @@ -211,7 +211,7 @@ func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *ModelGithubRepository) View() string { var baseStyle = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")).MarginLeft(1) + BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) @@ -307,6 +307,7 @@ func (m *ModelGithubRepository) viewSearchBar() string { // Define window style windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#3b698f")). Padding(0, 1). Width(m.skeleton.GetTerminalWidth() - 6).MarginLeft(1) diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger/ghtrigger.go index d273c18..bb6f9a7 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger/ghtrigger.go @@ -350,7 +350,7 @@ func (m *ModelGithubTrigger) View() string { if m.triggerFocused { baseStyle = baseStyle.BorderForeground(lipgloss.Color("240")) } else { - baseStyle = baseStyle.BorderForeground(lipgloss.Color("#00aaff")) + baseStyle = baseStyle.BorderForeground(lipgloss.Color("#3b698f")) } var tableWidth int @@ -520,8 +520,8 @@ func (m *ModelGithubTrigger) triggerButton() string { Align(lipgloss.Center) if m.triggerFocused { - button = button.BorderForeground(lipgloss.Color("#00aaff")). - Foreground(lipgloss.Color("#00aaff")). + button = button.BorderForeground(lipgloss.Color("#399adb")). + Foreground(lipgloss.Color("#399adb")). BorderStyle(lipgloss.DoubleBorder()) } @@ -623,6 +623,7 @@ func (m *ModelGithubTrigger) emptySelector() string { // Define window style windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#3b698f")). Padding(0, 1). Width(m.skeleton.GetTerminalWidth() - 18).MarginLeft(1) @@ -636,6 +637,7 @@ func (m *ModelGithubTrigger) inputSelector() string { // Define window style windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#3b698f")). Padding(0, 1). Width(m.skeleton.GetTerminalWidth() - 18).MarginLeft(1) @@ -648,6 +650,7 @@ func (m *ModelGithubTrigger) optionSelector() string { // Define window style windowStyle := lipgloss.NewStyle(). Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#3b698f")). Padding(0, 1). Width(m.skeleton.GetTerminalWidth() - 18).MarginLeft(1) diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow/ghworkflow.go index 9913642..aad5d1a 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow/ghworkflow.go @@ -55,7 +55,7 @@ func SetupModelGithubWorkflow(skeleton *skeleton.Skeleton, githubUseCase gu.UseC s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")). + BorderForeground(lipgloss.Color("#3b698f")). BorderBottom(true). Bold(false) s.Selected = s.Selected. @@ -111,7 +111,7 @@ func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m *ModelGithubWorkflow) View() string { var style = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")).MarginLeft(1) + BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go index aa951cb..76d0dfd 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go @@ -52,7 +52,6 @@ type ModelGithubWorkflowHistory struct { modelTabOptions *taboptions.Options updateSelfChan chan UpdateWorkflowHistoryMsg - blinkTableChan chan UpdateTableStyleMsg } func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { @@ -84,7 +83,7 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase var tableStyle = lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("240")).MarginLeft(1) + BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) modelError := hdlerror.SetupModelError(skeleton) tabOptions := taboptions.NewOptions(&modelError) @@ -103,7 +102,6 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase syncWorkflowHistoryContext: context.Background(), cancelSyncWorkflowHistory: func() {}, updateSelfChan: make(chan UpdateWorkflowHistoryMsg), - blinkTableChan: make(chan UpdateTableStyleMsg), tableStyle: tableStyle, } } @@ -123,17 +121,6 @@ func (m *ModelGithubWorkflowHistory) SelfUpdater() tea.Cmd { } } -func (m *ModelGithubWorkflowHistory) TableUpdater() tea.Cmd { - return func() tea.Msg { - select { - case o := <-m.blinkTableChan: - return o - default: - return nil - } - } -} - func (m *ModelGithubWorkflowHistory) ToggleLiveMode() { // send UpdateWorkflowHistoryMsg to update the workflow history every 5 seconds with ticker // send only if liveMode is true @@ -150,37 +137,9 @@ func (m *ModelGithubWorkflowHistory) ToggleLiveMode() { }() } -type UpdateTableStyleMsg struct { - Style lipgloss.Style -} - -func (m *ModelGithubWorkflowHistory) BlinkTable() { - // send UpdateTableStyleMsg to update the table style every 1 second with ticker - go func() { - updateAfter := time.Second * 1 - t := time.NewTicker(updateAfter) - - var red bool - for { - select { - case <-t.C: - if m.liveMode { - if red { - m.sendChangeTableColorMessage("112") - } else { - m.sendChangeTableColorMessage("#00aaff") - } - red = !red - } - } - } - }() -} - func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { m.setupOptions() m.ToggleLiveMode() - m.BlinkTable() return tea.Batch( m.modelTabOptions.Init(), func() tea.Msg { @@ -288,9 +247,10 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.liveMode = !m.liveMode if m.liveMode { m.modelError.SetSuccessMessage("Live mode enabled") + m.skeleton.UpdateWidgetValue("live", "Live Mode: On") } else { m.modelError.SetSuccessMessage("Live mode disabled") - m.sendChangeTableColorMessage("240") + m.skeleton.UpdateWidgetValue("live", "Live Mode: Off") } } case UpdateWorkflowHistoryMsg: @@ -298,12 +258,9 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { time.Sleep(msg.UpdateAfter) m.syncWorkflowHistory(m.syncWorkflowHistoryContext) }() - case UpdateTableStyleMsg: - m.tableStyle = msg.Style } cmds = append(cmds, m.SelfUpdater()) - cmds = append(cmds, m.TableUpdater()) m.modelError, cmd = m.modelError.Update(msg) cmds = append(cmds, cmd) @@ -317,12 +274,6 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } -func (m *ModelGithubWorkflowHistory) sendChangeTableColorMessage(color string) { - go func() { - m.blinkTableChan <- UpdateTableStyleMsg{Style: m.tableStyle.BorderForeground(lipgloss.Color(color))} - }() -} - func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { m.tableReady = false m.modelError.Reset() diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index 5d607e6..5458f11 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -11,6 +11,7 @@ import ( hdltypes "github.com/termkit/gama/internal/terminal/handler/types" pkgversion "github.com/termkit/gama/pkg/version" "github.com/termkit/skeleton" + "time" ) func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Model { @@ -22,9 +23,13 @@ func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Mod AddPage("workflow", "Workflow", hdlWorkflow.SetupModelGithubWorkflow(s, githubUseCase)). AddPage("trigger", "Trigger", hdltrigger.SetupModelGithubTrigger(s, githubUseCase)) - s.SetBorderColor("49").SetActiveTabBorderColor("#ff0055") + s.SetBorderColor("#ff0055").SetActiveTabBorderColor("#ff0055").SetInactiveTabBorderColor("#82636f").SetWidgetBorderColor("#ff0055") - s.AddWidget("version", "development mode") + //s.AddWidget("version", "development mode") + time.Sleep(100 * time.Millisecond) + s.AddWidget("repositories", "Repository Count: 0") + time.Sleep(100 * time.Millisecond) + s.AddWidget("live", "Live Mode: Off") s.SetTerminalViewportWidth(hdltypes.MinTerminalWidth) s.SetTerminalViewportHeight(hdltypes.MinTerminalHeight) diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/information/information.go index 783d8cc..a329292 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/information/information.go @@ -117,6 +117,12 @@ func (m *ModelInfo) View() string { helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + requiredNewLinesForCenter := m.skeleton.GetTerminalHeight()/2 - 11 + if requiredNewLinesForCenter < 0 { + requiredNewLinesForCenter = 0 + } + infoDoc.WriteString(strings.Repeat("\n", requiredNewLinesForCenter)) + infoDoc.WriteString(lipgloss.JoinVertical(lipgloss.Center, applicationName, applicationDescription, newVersionAvailableMsg)) docHeight := lipgloss.Height(infoDoc.String()) From fc1445a2c3c1fe3726294a0a1842d426b4da1e2f Mon Sep 17 00:00:00 2001 From: Engin Date: Mon, 2 Sep 2024 22:55:07 +0300 Subject: [PATCH 22/51] Improve code style --- go.mod | 4 + go.work.sum | 155 +++++++++++++++++++++++ internal/terminal/handler/error/error.go | 13 +- 3 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 go.work.sum diff --git a/go.mod b/go.mod index 2313b28..6b96a12 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,10 @@ module github.com/termkit/gama go 1.22.6 +replace ( + github.com/termkit/skeleton => ../skeleton +) + require ( github.com/Masterminds/semver/v3 v3.3.0 github.com/charmbracelet/bubbles v0.19.0 diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..1e16e82 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,155 @@ +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8= +cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= +cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= +cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= +cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/x/input v0.1.3 h1:oy4TMhyGQsYs/WWJwu1ELUMFnjiUAXwtDf048fHbCkg= +github.com/charmbracelet/x/input v0.1.3/go.mod h1:1gaCOyw1KI9e2j00j/BBZ4ErzRZqa05w0Ghn83yIhKU= +github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg= +github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 h1:zC34cGQu69FG7qzJ3WiKW244WfhDC3xxYMeNOX2gtUQ= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8= +github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nats-io/nats.go v1.34.0 h1:fnxnPCNiwIG5w08rlMcEKTUw4AV/nKyGCOJE8TdhSPk= +github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/sagikazarmark/crypt v0.19.0 h1:WMyLTjHBo64UvNcWqpzY3pbZTYgnemZU8FBZigKc42E= +github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= +go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= +go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= +go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= +go.etcd.io/etcd/client/v2 v2.305.12 h1:0m4ovXYo1CHaA/Mp3X/Fak5sRNIWf01wk/X1/G3sGKI= +go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E= +go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg= +go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= +google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/error/error.go index 37d5328..21ef236 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/error/error.go @@ -73,14 +73,16 @@ func (m *ModelError) Update(msg tea.Msg) (*ModelError, tea.Cmd) { case spinner.TickMsg: m.spinner, cmd = m.spinner.Update(msg) cmds = append(cmds, cmd) + + cmds = append(cmds, m.SelfUpdater()) case UpdateSelf: if msg.InProgress { m.spinner, cmd = m.spinner.Update(m.spinner.Tick()) cmds = append(cmds, cmd) } - } - cmds = append(cmds, m.SelfUpdater()) + cmds = append(cmds, m.SelfUpdater()) + } return m, tea.Batch(cmds...) } @@ -120,12 +122,7 @@ func (m *ModelError) View() string { func (m *ModelError) SelfUpdater() tea.Cmd { return func() tea.Msg { - select { - case o := <-m.updateChan: - return o - default: - return nil - } + return <-m.updateChan } } From 660ccd98a1974eca2fe0e210f5a3af265cae8399 Mon Sep 17 00:00:00 2001 From: Engin Date: Mon, 2 Sep 2024 22:56:08 +0300 Subject: [PATCH 23/51] Revert "Improve code style" This reverts commit fc1445a2c3c1fe3726294a0a1842d426b4da1e2f. modified: internal/terminal/handler/error/error.go --- go.mod | 4 - go.work.sum | 155 ----------------------- internal/terminal/handler/error/error.go | 13 +- 3 files changed, 8 insertions(+), 164 deletions(-) delete mode 100644 go.work.sum diff --git a/go.mod b/go.mod index 6b96a12..2313b28 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,6 @@ module github.com/termkit/gama go 1.22.6 -replace ( - github.com/termkit/skeleton => ../skeleton -) - require ( github.com/Masterminds/semver/v3 v3.3.0 github.com/charmbracelet/bubbles v0.19.0 diff --git a/go.work.sum b/go.work.sum deleted file mode 100644 index 1e16e82..0000000 --- a/go.work.sum +++ /dev/null @@ -1,155 +0,0 @@ -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8= -cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= -cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= -cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= -cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= -github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= -github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/x/input v0.1.3 h1:oy4TMhyGQsYs/WWJwu1ELUMFnjiUAXwtDf048fHbCkg= -github.com/charmbracelet/x/input v0.1.3/go.mod h1:1gaCOyw1KI9e2j00j/BBZ4ErzRZqa05w0Ghn83yIhKU= -github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg= -github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= -github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= -github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 h1:zC34cGQu69FG7qzJ3WiKW244WfhDC3xxYMeNOX2gtUQ= -github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8= -github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= -github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/nats-io/nats.go v1.34.0 h1:fnxnPCNiwIG5w08rlMcEKTUw4AV/nKyGCOJE8TdhSPk= -github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= -github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= -github.com/sagikazarmark/crypt v0.19.0 h1:WMyLTjHBo64UvNcWqpzY3pbZTYgnemZU8FBZigKc42E= -github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= -github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= -github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= -go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= -go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= -go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= -go.etcd.io/etcd/client/v2 v2.305.12 h1:0m4ovXYo1CHaA/Mp3X/Fak5sRNIWf01wk/X1/G3sGKI= -go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E= -go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg= -go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= -google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/error/error.go index 21ef236..37d5328 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/error/error.go @@ -73,17 +73,15 @@ func (m *ModelError) Update(msg tea.Msg) (*ModelError, tea.Cmd) { case spinner.TickMsg: m.spinner, cmd = m.spinner.Update(msg) cmds = append(cmds, cmd) - - cmds = append(cmds, m.SelfUpdater()) case UpdateSelf: if msg.InProgress { m.spinner, cmd = m.spinner.Update(m.spinner.Tick()) cmds = append(cmds, cmd) } - - cmds = append(cmds, m.SelfUpdater()) } + cmds = append(cmds, m.SelfUpdater()) + return m, tea.Batch(cmds...) } @@ -122,7 +120,12 @@ func (m *ModelError) View() string { func (m *ModelError) SelfUpdater() tea.Cmd { return func() tea.Msg { - return <-m.updateChan + select { + case o := <-m.updateChan: + return o + default: + return nil + } } } From 7b538bc0630dbae959182a014ceb985cfdee89a1 Mon Sep 17 00:00:00 2001 From: Engin Date: Mon, 2 Sep 2024 22:58:17 +0300 Subject: [PATCH 24/51] Reapply "Improve code style" This reverts commit 660ccd98a1974eca2fe0e210f5a3af265cae8399. modified: go.mod new file: go.work.sum --- go.mod | 4 + go.work.sum | 155 +++++++++++++++++++++++ internal/terminal/handler/error/error.go | 13 +- 3 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 go.work.sum diff --git a/go.mod b/go.mod index 2313b28..6b96a12 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,10 @@ module github.com/termkit/gama go 1.22.6 +replace ( + github.com/termkit/skeleton => ../skeleton +) + require ( github.com/Masterminds/semver/v3 v3.3.0 github.com/charmbracelet/bubbles v0.19.0 diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..1e16e82 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,155 @@ +cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= +cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8= +cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk= +cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= +cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= +cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= +cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= +cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= +cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/x/input v0.1.3 h1:oy4TMhyGQsYs/WWJwu1ELUMFnjiUAXwtDf048fHbCkg= +github.com/charmbracelet/x/input v0.1.3/go.mod h1:1gaCOyw1KI9e2j00j/BBZ4ErzRZqa05w0Ghn83yIhKU= +github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg= +github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= +github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 h1:zC34cGQu69FG7qzJ3WiKW244WfhDC3xxYMeNOX2gtUQ= +github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8= +github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nats-io/nats.go v1.34.0 h1:fnxnPCNiwIG5w08rlMcEKTUw4AV/nKyGCOJE8TdhSPk= +github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/sagikazarmark/crypt v0.19.0 h1:WMyLTjHBo64UvNcWqpzY3pbZTYgnemZU8FBZigKc42E= +github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= +github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= +github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= +go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= +go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= +go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= +go.etcd.io/etcd/client/v2 v2.305.12 h1:0m4ovXYo1CHaA/Mp3X/Fak5sRNIWf01wk/X1/G3sGKI= +go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E= +go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg= +go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= +google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= +google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= +google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/error/error.go index 37d5328..21ef236 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/error/error.go @@ -73,14 +73,16 @@ func (m *ModelError) Update(msg tea.Msg) (*ModelError, tea.Cmd) { case spinner.TickMsg: m.spinner, cmd = m.spinner.Update(msg) cmds = append(cmds, cmd) + + cmds = append(cmds, m.SelfUpdater()) case UpdateSelf: if msg.InProgress { m.spinner, cmd = m.spinner.Update(m.spinner.Tick()) cmds = append(cmds, cmd) } - } - cmds = append(cmds, m.SelfUpdater()) + cmds = append(cmds, m.SelfUpdater()) + } return m, tea.Batch(cmds...) } @@ -120,12 +122,7 @@ func (m *ModelError) View() string { func (m *ModelError) SelfUpdater() tea.Cmd { return func() tea.Msg { - select { - case o := <-m.updateChan: - return o - default: - return nil - } + return <-m.updateChan } } From 8dc8680ba94bc1f8da6b2784e387c1dc62c70508 Mon Sep 17 00:00:00 2001 From: Engin Date: Mon, 2 Sep 2024 23:27:29 +0300 Subject: [PATCH 25/51] Make architecture basic --- go.mod | 4 - internal/terminal/handler/error/error.go | 2 +- .../information.go => ghinformation.go} | 8 +- .../{ghrepository => }/ghrepository.go | 12 +- .../terminal/handler/ghrepository/keymap.go | 55 ---- .../terminal/handler/ghrepository/table.go | 12 - .../handler/{ghtrigger => }/ghtrigger.go | 12 +- internal/terminal/handler/ghtrigger/keymap.go | 61 ----- internal/terminal/handler/ghtrigger/table.go | 14 - .../handler/{ghworkflow => }/ghworkflow.go | 9 +- .../terminal/handler/ghworkflow/keymap.go | 43 ---- internal/terminal/handler/ghworkflow/table.go | 10 - .../ghworkflowhistory.go | 9 +- .../handler/ghworkflowhistory/keymap.go | 61 ----- .../handler/ghworkflowhistory/table.go | 14 - internal/terminal/handler/handler.go | 21 +- .../terminal/handler/information/keymap.go | 49 ---- internal/terminal/handler/keymap.go | 241 +++++++++++++++++- internal/terminal/handler/table.go | 39 +++ .../{style => handler/types}/style.go | 16 +- 20 files changed, 311 insertions(+), 381 deletions(-) rename internal/terminal/handler/{information/information.go => ghinformation.go} (96%) rename internal/terminal/handler/{ghrepository => }/ghrepository.go (97%) delete mode 100644 internal/terminal/handler/ghrepository/keymap.go delete mode 100644 internal/terminal/handler/ghrepository/table.go rename internal/terminal/handler/{ghtrigger => }/ghtrigger.go (98%) delete mode 100644 internal/terminal/handler/ghtrigger/keymap.go delete mode 100644 internal/terminal/handler/ghtrigger/table.go rename internal/terminal/handler/{ghworkflow => }/ghworkflow.go (96%) delete mode 100644 internal/terminal/handler/ghworkflow/keymap.go delete mode 100644 internal/terminal/handler/ghworkflow/table.go rename internal/terminal/handler/{ghworkflowhistory => }/ghworkflowhistory.go (98%) delete mode 100644 internal/terminal/handler/ghworkflowhistory/keymap.go delete mode 100644 internal/terminal/handler/ghworkflowhistory/table.go delete mode 100644 internal/terminal/handler/information/keymap.go create mode 100644 internal/terminal/handler/table.go rename internal/terminal/{style => handler/types}/style.go (70%) diff --git a/go.mod b/go.mod index 6b96a12..2313b28 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,6 @@ module github.com/termkit/gama go 1.22.6 -replace ( - github.com/termkit/skeleton => ../skeleton -) - require ( github.com/Masterminds/semver/v3 v3.3.0 github.com/charmbracelet/bubbles v0.19.0 diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/error/error.go index 21ef236..cbc31e7 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/error/error.go @@ -5,7 +5,7 @@ import ( "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - ts "github.com/termkit/gama/internal/terminal/style" + ts "github.com/termkit/gama/internal/terminal/handler/types" "github.com/termkit/skeleton" "strings" ) diff --git a/internal/terminal/handler/information/information.go b/internal/terminal/handler/ghinformation.go similarity index 96% rename from internal/terminal/handler/information/information.go rename to internal/terminal/handler/ghinformation.go index a329292..9be812d 100644 --- a/internal/terminal/handler/information/information.go +++ b/internal/terminal/handler/ghinformation.go @@ -1,4 +1,4 @@ -package information +package handler import ( "context" @@ -8,7 +8,7 @@ import ( "github.com/charmbracelet/lipgloss" gu "github.com/termkit/gama/internal/github/usecase" hdlerror "github.com/termkit/gama/internal/terminal/handler/error" - ts "github.com/termkit/gama/internal/terminal/style" + ts "github.com/termkit/gama/internal/terminal/handler/types" pkgversion "github.com/termkit/gama/pkg/version" "github.com/termkit/skeleton" "strings" @@ -26,7 +26,7 @@ type ModelInfo struct { modelError *hdlerror.ModelError // keymap - Keys keyMap + Keys githubInformationKeyMap updateChan chan updateSelf } @@ -62,7 +62,7 @@ func SetupModelInfo(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase, versi github: githubUseCase, version: version, Help: help.New(), - Keys: keys, + Keys: githubInformationKeys, modelError: &modelError, } } diff --git a/internal/terminal/handler/ghrepository/ghrepository.go b/internal/terminal/handler/ghrepository.go similarity index 97% rename from internal/terminal/handler/ghrepository/ghrepository.go rename to internal/terminal/handler/ghrepository.go index c4af5f9..17c1653 100644 --- a/internal/terminal/handler/ghrepository/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -1,4 +1,4 @@ -package ghrepository +package handler import ( "context" @@ -15,7 +15,6 @@ import ( hdlerror "github.com/termkit/gama/internal/terminal/handler/error" "github.com/termkit/gama/internal/terminal/handler/taboptions" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" - ts "github.com/termkit/gama/internal/terminal/style" "github.com/termkit/gama/pkg/browser" "github.com/termkit/skeleton" "strconv" @@ -36,7 +35,7 @@ type ModelGithubRepository struct { github gu.UseCase // keymap - Keys keyMap + Keys githubRepositoryKeyMap // models Help help.Model @@ -113,7 +112,7 @@ func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.Us return &ModelGithubRepository{ skeleton: skeleton, Help: help.New(), - Keys: keys, + Keys: githubRepositoryKeys, github: githubUseCase, tableGithubRepository: tableGithubRepository, modelError: &modelError, @@ -126,9 +125,6 @@ func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.Us } } -type updateSelf struct { -} - func (m *ModelGithubRepository) SelfUpdater() tea.Cmd { return func() tea.Msg { go func() { @@ -213,7 +209,7 @@ func (m *ModelGithubRepository) View() string { BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) - helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) var tableWidth int for _, t := range tableColumnsGithubRepository { diff --git a/internal/terminal/handler/ghrepository/keymap.go b/internal/terminal/handler/ghrepository/keymap.go deleted file mode 100644 index c6fecc7..0000000 --- a/internal/terminal/handler/ghrepository/keymap.go +++ /dev/null @@ -1,55 +0,0 @@ -package ghrepository - -import ( - "fmt" - - "github.com/termkit/gama/internal/config" - - teakey "github.com/charmbracelet/bubbles/key" -) - -type keyMap struct { - Refresh teakey.Binding - LaunchTab teakey.Binding - SwitchTab teakey.Binding -} - -func (k keyMap) ShortHelp() []teakey.Binding { - return []teakey.Binding{k.SwitchTab, k.Refresh, k.LaunchTab} -} - -func (k keyMap) FullHelp() [][]teakey.Binding { - return [][]teakey.Binding{ - {k.SwitchTab}, - {k.Refresh}, - {k.LaunchTab}, - } -} - -var keys = func() keyMap { - cfg, err := config.LoadConfig() - if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) - } - - var tabSwitch = fmt.Sprintf("%s | %s", cfg.Shortcuts.SwitchTabLeft, cfg.Shortcuts.SwitchTabRight) - - return keyMap{ - Refresh: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Refresh), - teakey.WithHelp(cfg.Shortcuts.Refresh, "Refresh list"), - ), - LaunchTab: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Enter), - teakey.WithHelp(cfg.Shortcuts.Enter, "Launch the selected option"), - ), - SwitchTab: teakey.NewBinding( - teakey.WithKeys(""), // help-only binding - teakey.WithHelp(tabSwitch, "switch tab"), - ), - } -}() - -func (m *ModelGithubRepository) ViewHelp() string { - return m.Help.View(m.Keys) -} diff --git a/internal/terminal/handler/ghrepository/table.go b/internal/terminal/handler/ghrepository/table.go deleted file mode 100644 index 256735e..0000000 --- a/internal/terminal/handler/ghrepository/table.go +++ /dev/null @@ -1,12 +0,0 @@ -package ghrepository - -import ( - "github.com/charmbracelet/bubbles/table" -) - -var tableColumnsGithubRepository = []table.Column{ - {Title: "Repository", Width: 24}, - {Title: "Default Branch", Width: 16}, - {Title: "Stars", Width: 6}, - {Title: "Workflows", Width: 9}, -} diff --git a/internal/terminal/handler/ghtrigger/ghtrigger.go b/internal/terminal/handler/ghtrigger.go similarity index 98% rename from internal/terminal/handler/ghtrigger/ghtrigger.go rename to internal/terminal/handler/ghtrigger.go index bb6f9a7..2a27f2a 100644 --- a/internal/terminal/handler/ghtrigger/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -1,4 +1,4 @@ -package ghtrigger +package handler import ( "context" @@ -11,9 +11,7 @@ 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" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" - ts "github.com/termkit/gama/internal/terminal/style" "github.com/termkit/gama/pkg/workflow" "github.com/termkit/skeleton" "slices" @@ -45,7 +43,7 @@ type ModelGithubTrigger struct { github gu.UseCase // keymap - Keys keyMap + Keys githubTriggerKeyMap // models Help help.Model @@ -84,7 +82,7 @@ func SetupModelGithubTrigger(skeleton *skeleton.Skeleton, githubUseCase gu.UseCa return &ModelGithubTrigger{ skeleton: skeleton, Help: help.New(), - Keys: keys, + Keys: githubTriggerKeys, github: githubUseCase, SelectedRepository: hdltypes.NewSelectedRepository(), modelError: &modelError, @@ -171,7 +169,7 @@ func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.triggerFocused && m.isTriggerable { go m.triggerWorkflow() return m, func() tea.Msg { - return ghworkflowhistory.UpdateWorkflowHistoryMsg{UpdateAfter: time.Second * 5} + return UpdateWorkflowHistoryMsg{UpdateAfter: time.Second * 5} } } } @@ -345,7 +343,7 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { func (m *ModelGithubTrigger) View() string { baseStyle := lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).MarginLeft(1) - helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) if m.triggerFocused { baseStyle = baseStyle.BorderForeground(lipgloss.Color("240")) diff --git a/internal/terminal/handler/ghtrigger/keymap.go b/internal/terminal/handler/ghtrigger/keymap.go deleted file mode 100644 index e0682f7..0000000 --- a/internal/terminal/handler/ghtrigger/keymap.go +++ /dev/null @@ -1,61 +0,0 @@ -package ghtrigger - -import ( - "fmt" - - "github.com/termkit/gama/internal/config" - - teakey "github.com/charmbracelet/bubbles/key" -) - -type keyMap struct { - SwitchTabLeft teakey.Binding - SwitchTab teakey.Binding - Trigger teakey.Binding - Refresh teakey.Binding -} - -func (k keyMap) ShortHelp() []teakey.Binding { - return []teakey.Binding{k.SwitchTabLeft, k.Refresh, k.SwitchTab, k.Trigger} -} - -func (k keyMap) FullHelp() [][]teakey.Binding { - return [][]teakey.Binding{ - {k.SwitchTabLeft}, - {k.Refresh}, - {k.SwitchTab}, - {k.Trigger}, - } -} - -var keys = func() keyMap { - cfg, err := config.LoadConfig() - if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) - } - - previousTab := cfg.Shortcuts.SwitchTabLeft - - return keyMap{ - SwitchTabLeft: teakey.NewBinding( - teakey.WithKeys(""), // help-only binding - teakey.WithHelp(previousTab, "previous tab"), - ), - Refresh: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Refresh), - teakey.WithHelp(cfg.Shortcuts.Refresh, "Refresh list"), - ), - SwitchTab: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Tab), - teakey.WithHelp(cfg.Shortcuts.Tab, "switch button"), - ), - Trigger: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Enter), - teakey.WithHelp(cfg.Shortcuts.Enter, "trigger workflow"), - ), - } -}() - -func (m *ModelGithubTrigger) ViewHelp() string { - return m.Help.View(m.Keys) -} diff --git a/internal/terminal/handler/ghtrigger/table.go b/internal/terminal/handler/ghtrigger/table.go deleted file mode 100644 index 538bdc3..0000000 --- a/internal/terminal/handler/ghtrigger/table.go +++ /dev/null @@ -1,14 +0,0 @@ -package ghtrigger - -import ( - "github.com/charmbracelet/bubbles/table" -) - -var tableColumnsTrigger = []table.Column{ - {Title: "ID", Width: 2}, - {Title: "Type", Width: 6}, - {Title: "Key", Width: 24}, - {Title: "Default", Width: 16}, - //{Title: "Description", Width: 64}, - {Title: "Value", Width: 44}, -} diff --git a/internal/terminal/handler/ghworkflow/ghworkflow.go b/internal/terminal/handler/ghworkflow.go similarity index 96% rename from internal/terminal/handler/ghworkflow/ghworkflow.go rename to internal/terminal/handler/ghworkflow.go index aad5d1a..8b1f4eb 100644 --- a/internal/terminal/handler/ghworkflow/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow.go @@ -1,10 +1,9 @@ -package ghworkflow +package handler import ( "context" "errors" "fmt" - ts "github.com/termkit/gama/internal/terminal/style" "github.com/termkit/skeleton" "sort" "strings" @@ -34,7 +33,7 @@ type ModelGithubWorkflow struct { github gu.UseCase // keymap - Keys keyMap + Keys githubWorkflowKeyMap // models Help help.Model @@ -69,7 +68,7 @@ func SetupModelGithubWorkflow(skeleton *skeleton.Skeleton, githubUseCase gu.UseC return &ModelGithubWorkflow{ skeleton: skeleton, Help: help.New(), - Keys: keys, + Keys: githubWorkflowKeys, github: githubUseCase, modelError: &modelError, tableTriggerableWorkflow: tableTriggerableWorkflow, @@ -113,7 +112,7 @@ func (m *ModelGithubWorkflow) View() string { BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) - helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) termWidth := m.skeleton.GetTerminalWidth() termHeight := m.skeleton.GetTerminalHeight() diff --git a/internal/terminal/handler/ghworkflow/keymap.go b/internal/terminal/handler/ghworkflow/keymap.go deleted file mode 100644 index 5e6f61b..0000000 --- a/internal/terminal/handler/ghworkflow/keymap.go +++ /dev/null @@ -1,43 +0,0 @@ -package ghworkflow - -import ( - "fmt" - - "github.com/termkit/gama/internal/config" - - teakey "github.com/charmbracelet/bubbles/key" -) - -type keyMap struct { - SwitchTab teakey.Binding -} - -func (k keyMap) ShortHelp() []teakey.Binding { - return []teakey.Binding{k.SwitchTab} -} - -func (k keyMap) FullHelp() [][]teakey.Binding { - return [][]teakey.Binding{ - {k.SwitchTab}, - } -} - -var keys = func() keyMap { - cfg, err := config.LoadConfig() - if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) - } - - var tabSwitch = fmt.Sprintf("%s | %s", cfg.Shortcuts.SwitchTabLeft, cfg.Shortcuts.SwitchTabRight) - - return keyMap{ - SwitchTab: teakey.NewBinding( - teakey.WithKeys(""), // help-only binding - teakey.WithHelp(tabSwitch, "switch tab"), - ), - } -}() - -func (m *ModelGithubWorkflow) ViewHelp() string { - return m.Help.View(m.Keys) -} diff --git a/internal/terminal/handler/ghworkflow/table.go b/internal/terminal/handler/ghworkflow/table.go deleted file mode 100644 index 344eeff..0000000 --- a/internal/terminal/handler/ghworkflow/table.go +++ /dev/null @@ -1,10 +0,0 @@ -package ghworkflow - -import ( - "github.com/charmbracelet/bubbles/table" -) - -var tableColumnsWorkflow = []table.Column{ - {Title: "Workflow", Width: 32}, - {Title: "File", Width: 48}, -} diff --git a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go similarity index 98% rename from internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go rename to internal/terminal/handler/ghworkflowhistory.go index 76d0dfd..7adb49b 100644 --- a/internal/terminal/handler/ghworkflowhistory/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -1,11 +1,10 @@ -package ghworkflowhistory +package handler import ( "context" "errors" "fmt" "github.com/termkit/gama/internal/config" - ts "github.com/termkit/gama/internal/terminal/style" "github.com/termkit/skeleton" "strings" "time" @@ -43,7 +42,7 @@ type ModelGithubWorkflowHistory struct { github gu.UseCase // keymap - Keys keyMap + Keys githubWorkflowHistoryKeyMap // models Help help.Model @@ -93,7 +92,7 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase liveMode: cfg.Settings.LiveMode.Enabled, liveModeInterval: cfg.Settings.LiveMode.Interval, Help: help.New(), - Keys: keys, + Keys: githubWorkflowHistoryKeys, github: githubUseCase, tableWorkflowHistory: tableWorkflowHistory, modelError: &modelError, @@ -328,7 +327,7 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { } func (m *ModelGithubWorkflowHistory) View() string { - helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) termWidth := m.skeleton.GetTerminalWidth() termHeight := m.skeleton.GetTerminalHeight() diff --git a/internal/terminal/handler/ghworkflowhistory/keymap.go b/internal/terminal/handler/ghworkflowhistory/keymap.go deleted file mode 100644 index f5766c0..0000000 --- a/internal/terminal/handler/ghworkflowhistory/keymap.go +++ /dev/null @@ -1,61 +0,0 @@ -package ghworkflowhistory - -import ( - "fmt" - - "github.com/termkit/gama/internal/config" - - teakey "github.com/charmbracelet/bubbles/key" -) - -type keyMap struct { - LaunchTab teakey.Binding - Refresh teakey.Binding - SwitchTab teakey.Binding - LiveMode teakey.Binding -} - -func (k keyMap) ShortHelp() []teakey.Binding { - return []teakey.Binding{k.SwitchTab, k.Refresh, k.LaunchTab, k.LiveMode} -} - -func (k keyMap) FullHelp() [][]teakey.Binding { - return [][]teakey.Binding{ - {k.SwitchTab}, - {k.Refresh}, - {k.LaunchTab}, - {k.LiveMode}, - } -} - -var keys = func() keyMap { - cfg, err := config.LoadConfig() - if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) - } - - var tabSwitch = fmt.Sprintf("%s | %s", cfg.Shortcuts.SwitchTabLeft, cfg.Shortcuts.SwitchTabRight) - - return keyMap{ - Refresh: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Refresh), - teakey.WithHelp(cfg.Shortcuts.Refresh, "Refresh list"), - ), - LaunchTab: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Enter), - teakey.WithHelp(cfg.Shortcuts.Enter, "Launch the selected option"), - ), - LiveMode: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.LiveMode), - teakey.WithHelp(cfg.Shortcuts.LiveMode, "Toggle live mode"), - ), - SwitchTab: teakey.NewBinding( - teakey.WithKeys(""), // help-only binding - teakey.WithHelp(tabSwitch, "switch tab"), - ), - } -}() - -func (m *ModelGithubWorkflowHistory) ViewHelp() string { - return m.Help.View(m.Keys) -} diff --git a/internal/terminal/handler/ghworkflowhistory/table.go b/internal/terminal/handler/ghworkflowhistory/table.go deleted file mode 100644 index 18ada96..0000000 --- a/internal/terminal/handler/ghworkflowhistory/table.go +++ /dev/null @@ -1,14 +0,0 @@ -package ghworkflowhistory - -import ( - "github.com/charmbracelet/bubbles/table" -) - -var tableColumnsWorkflowHistory = []table.Column{ - {Title: "Workflow", Width: 12}, - {Title: "Commit Message", Width: 16}, - {Title: "Triggered", Width: 12}, - {Title: "Started At", Width: 19}, - {Title: "Status", Width: 9}, - {Title: "Duration", Width: 8}, -} diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index 5458f11..62a8a0a 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -3,11 +3,6 @@ package handler import ( tea "github.com/charmbracelet/bubbletea" gu "github.com/termkit/gama/internal/github/usecase" - 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" - hdlworkflowhistory "github.com/termkit/gama/internal/terminal/handler/ghworkflowhistory" - hdlinfo "github.com/termkit/gama/internal/terminal/handler/information" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" pkgversion "github.com/termkit/gama/pkg/version" "github.com/termkit/skeleton" @@ -17,11 +12,11 @@ import ( func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Model { s := skeleton.NewSkeleton() - s.AddPage("info", "Info", hdlinfo.SetupModelInfo(s, githubUseCase, version)). - AddPage("repository", "Repository", hdlgithubrepo.SetupModelGithubRepository(s, githubUseCase)). - AddPage("history", "Workflow History", hdlworkflowhistory.SetupModelGithubWorkflowHistory(s, githubUseCase)). - AddPage("workflow", "Workflow", hdlWorkflow.SetupModelGithubWorkflow(s, githubUseCase)). - AddPage("trigger", "Trigger", hdltrigger.SetupModelGithubTrigger(s, githubUseCase)) + s.AddPage("info", "Info", SetupModelInfo(s, githubUseCase, version)). + AddPage("repository", "Repository", SetupModelGithubRepository(s, githubUseCase)). + AddPage("history", "Workflow History", SetupModelGithubWorkflowHistory(s, githubUseCase)). + AddPage("workflow", "Workflow", SetupModelGithubWorkflow(s, githubUseCase)). + AddPage("trigger", "Trigger", SetupModelGithubTrigger(s, githubUseCase)) s.SetBorderColor("#ff0055").SetActiveTabBorderColor("#ff0055").SetInactiveTabBorderColor("#82636f").SetWidgetBorderColor("#ff0055") @@ -34,9 +29,9 @@ func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Mod s.SetTerminalViewportWidth(hdltypes.MinTerminalWidth) s.SetTerminalViewportHeight(hdltypes.MinTerminalHeight) - s.KeyMap.SetKeyNextTab(keys.SwitchTabRight) - s.KeyMap.SetKeyPrevTab(keys.SwitchTabLeft) - s.KeyMap.SetKeyQuit(keys.Quit) + s.KeyMap.SetKeyNextTab(handlerKeys.SwitchTabRight) + s.KeyMap.SetKeyPrevTab(handlerKeys.SwitchTabLeft) + s.KeyMap.SetKeyQuit(handlerKeys.Quit) return s } diff --git a/internal/terminal/handler/information/keymap.go b/internal/terminal/handler/information/keymap.go deleted file mode 100644 index ad1ec59..0000000 --- a/internal/terminal/handler/information/keymap.go +++ /dev/null @@ -1,49 +0,0 @@ -package information - -import ( - "fmt" - - "github.com/termkit/gama/internal/config" - - teakey "github.com/charmbracelet/bubbles/key" -) - -type keyMap struct { - SwitchTabRight teakey.Binding - Quit teakey.Binding -} - -func (k keyMap) ShortHelp() []teakey.Binding { - return []teakey.Binding{k.SwitchTabRight, k.Quit} -} - -func (k keyMap) FullHelp() [][]teakey.Binding { - return [][]teakey.Binding{ - {k.SwitchTabRight}, - {k.Quit}, - } -} - -var keys = func() keyMap { - cfg, err := config.LoadConfig() - if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) - } - - switchTabRight := cfg.Shortcuts.SwitchTabRight - - return keyMap{ - SwitchTabRight: teakey.NewBinding( - teakey.WithKeys(""), // help-only binding - teakey.WithHelp(switchTabRight, "next tab"), - ), - Quit: teakey.NewBinding( - teakey.WithKeys("q", cfg.Shortcuts.Quit), - teakey.WithHelp("q", "quit"), - ), - } -}() - -func (m *ModelInfo) ViewHelp() string { - return m.Help.View(m.Keys) -} diff --git a/internal/terminal/handler/keymap.go b/internal/terminal/handler/keymap.go index 034c703..14fa331 100644 --- a/internal/terminal/handler/keymap.go +++ b/internal/terminal/handler/keymap.go @@ -7,18 +7,26 @@ import ( teakey "github.com/charmbracelet/bubbles/key" ) -type keyMap struct { +func loadConfig() *config.Config { + cfg, err := config.LoadConfig() + if err != nil { + panic(fmt.Sprintf("failed to load config: %v", err)) + } + return cfg +} + +// --------------------------------------------------------------------------- + +type handlerKeyMap struct { SwitchTabRight teakey.Binding SwitchTabLeft teakey.Binding Quit teakey.Binding } -var keys = func() keyMap { - cfg, err := config.LoadConfig() - if err != nil { - panic(fmt.Sprintf("failed to load config: %v", err)) - } - return keyMap{ +var handlerKeys = func() handlerKeyMap { + cfg := loadConfig() + + return handlerKeyMap{ SwitchTabRight: teakey.NewBinding( teakey.WithKeys(cfg.Shortcuts.SwitchTabRight), ), @@ -30,3 +38,222 @@ var keys = func() keyMap { ), } }() + +// --------------------------------------------------------------------------- + +type githubInformationKeyMap struct { + SwitchTabRight teakey.Binding + Quit teakey.Binding +} + +func (k githubInformationKeyMap) ShortHelp() []teakey.Binding { + return []teakey.Binding{k.SwitchTabRight, k.Quit} +} + +func (k githubInformationKeyMap) FullHelp() [][]teakey.Binding { + return [][]teakey.Binding{ + {k.SwitchTabRight}, + {k.Quit}, + } +} + +var githubInformationKeys = func() githubInformationKeyMap { + cfg := loadConfig() + + switchTabRight := cfg.Shortcuts.SwitchTabRight + + return githubInformationKeyMap{ + SwitchTabRight: teakey.NewBinding( + teakey.WithKeys(""), // help-only binding + teakey.WithHelp(switchTabRight, "next tab"), + ), + Quit: teakey.NewBinding( + teakey.WithKeys("q", cfg.Shortcuts.Quit), + teakey.WithHelp("q", "quit"), + ), + } +}() + +func (m *ModelInfo) ViewHelp() string { + return m.Help.View(m.Keys) +} + +// --------------------------------------------------------------------------- + +type githubRepositoryKeyMap struct { + Refresh teakey.Binding + LaunchTab teakey.Binding + SwitchTab teakey.Binding +} + +func (k githubRepositoryKeyMap) ShortHelp() []teakey.Binding { + return []teakey.Binding{k.SwitchTab, k.Refresh, k.LaunchTab} +} + +func (k githubRepositoryKeyMap) FullHelp() [][]teakey.Binding { + return [][]teakey.Binding{ + {k.SwitchTab}, + {k.Refresh}, + {k.LaunchTab}, + } +} + +var githubRepositoryKeys = func() githubRepositoryKeyMap { + cfg := loadConfig() + + var tabSwitch = fmt.Sprintf("%s | %s", cfg.Shortcuts.SwitchTabLeft, cfg.Shortcuts.SwitchTabRight) + + return githubRepositoryKeyMap{ + Refresh: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.Refresh), + teakey.WithHelp(cfg.Shortcuts.Refresh, "Refresh list"), + ), + LaunchTab: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.Enter), + teakey.WithHelp(cfg.Shortcuts.Enter, "Launch the selected option"), + ), + SwitchTab: teakey.NewBinding( + teakey.WithKeys(""), // help-only binding + teakey.WithHelp(tabSwitch, "switch tab"), + ), + } +}() + +func (m *ModelGithubRepository) ViewHelp() string { + return m.Help.View(m.Keys) +} + +// --------------------------------------------------------------------------- + +type githubWorkflowHistoryKeyMap struct { + LaunchTab teakey.Binding + Refresh teakey.Binding + SwitchTab teakey.Binding + LiveMode teakey.Binding +} + +func (k githubWorkflowHistoryKeyMap) ShortHelp() []teakey.Binding { + return []teakey.Binding{k.SwitchTab, k.Refresh, k.LaunchTab, k.LiveMode} +} + +func (k githubWorkflowHistoryKeyMap) FullHelp() [][]teakey.Binding { + return [][]teakey.Binding{ + {k.SwitchTab}, + {k.Refresh}, + {k.LaunchTab}, + {k.LiveMode}, + } +} + +var githubWorkflowHistoryKeys = func() githubWorkflowHistoryKeyMap { + cfg := loadConfig() + + var tabSwitch = fmt.Sprintf("%s | %s", cfg.Shortcuts.SwitchTabLeft, cfg.Shortcuts.SwitchTabRight) + + return githubWorkflowHistoryKeyMap{ + Refresh: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.Refresh), + teakey.WithHelp(cfg.Shortcuts.Refresh, "Refresh list"), + ), + LaunchTab: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.Enter), + teakey.WithHelp(cfg.Shortcuts.Enter, "Launch the selected option"), + ), + LiveMode: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.LiveMode), + teakey.WithHelp(cfg.Shortcuts.LiveMode, "Toggle live mode"), + ), + SwitchTab: teakey.NewBinding( + teakey.WithKeys(""), // help-only binding + teakey.WithHelp(tabSwitch, "switch tab"), + ), + } +}() + +func (m *ModelGithubWorkflowHistory) ViewHelp() string { + return m.Help.View(m.Keys) +} + +// --------------------------------------------------------------------------- + +type githubWorkflowKeyMap struct { + SwitchTab teakey.Binding +} + +func (k githubWorkflowKeyMap) ShortHelp() []teakey.Binding { + return []teakey.Binding{k.SwitchTab} +} + +func (k githubWorkflowKeyMap) FullHelp() [][]teakey.Binding { + return [][]teakey.Binding{ + {k.SwitchTab}, + } +} + +var githubWorkflowKeys = func() githubWorkflowKeyMap { + cfg := loadConfig() + + var tabSwitch = fmt.Sprintf("%s | %s", cfg.Shortcuts.SwitchTabLeft, cfg.Shortcuts.SwitchTabRight) + + return githubWorkflowKeyMap{ + SwitchTab: teakey.NewBinding( + teakey.WithKeys(""), // help-only binding + teakey.WithHelp(tabSwitch, "switch tab"), + ), + } +}() + +func (m *ModelGithubWorkflow) ViewHelp() string { + return m.Help.View(m.Keys) +} + +// --------------------------------------------------------------------------- + +type githubTriggerKeyMap struct { + SwitchTabLeft teakey.Binding + SwitchTab teakey.Binding + Trigger teakey.Binding + Refresh teakey.Binding +} + +func (k githubTriggerKeyMap) ShortHelp() []teakey.Binding { + return []teakey.Binding{k.SwitchTabLeft, k.Refresh, k.SwitchTab, k.Trigger} +} + +func (k githubTriggerKeyMap) FullHelp() [][]teakey.Binding { + return [][]teakey.Binding{ + {k.SwitchTabLeft}, + {k.Refresh}, + {k.SwitchTab}, + {k.Trigger}, + } +} + +var githubTriggerKeys = func() githubTriggerKeyMap { + cfg := loadConfig() + + previousTab := cfg.Shortcuts.SwitchTabLeft + + return githubTriggerKeyMap{ + SwitchTabLeft: teakey.NewBinding( + teakey.WithKeys(""), // help-only binding + teakey.WithHelp(previousTab, "previous tab"), + ), + Refresh: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.Refresh), + teakey.WithHelp(cfg.Shortcuts.Refresh, "Refresh list"), + ), + SwitchTab: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.Tab), + teakey.WithHelp(cfg.Shortcuts.Tab, "switch button"), + ), + Trigger: teakey.NewBinding( + teakey.WithKeys(cfg.Shortcuts.Enter), + teakey.WithHelp(cfg.Shortcuts.Enter, "trigger workflow"), + ), + } +}() + +func (m *ModelGithubTrigger) ViewHelp() string { + return m.Help.View(m.Keys) +} diff --git a/internal/terminal/handler/table.go b/internal/terminal/handler/table.go new file mode 100644 index 0000000..ce59527 --- /dev/null +++ b/internal/terminal/handler/table.go @@ -0,0 +1,39 @@ +package handler + +import "github.com/charmbracelet/bubbles/table" + +var tableColumnsGithubRepository = []table.Column{ + {Title: "Repository", Width: 24}, + {Title: "Default Branch", Width: 16}, + {Title: "Stars", Width: 6}, + {Title: "Workflows", Width: 9}, +} + +// --------------------------------------------------------------------------- + +var tableColumnsTrigger = []table.Column{ + {Title: "ID", Width: 2}, + {Title: "Type", Width: 6}, + {Title: "Key", Width: 24}, + {Title: "Default", Width: 16}, + //{Title: "Description", Width: 64}, + {Title: "Value", Width: 44}, +} + +// --------------------------------------------------------------------------- + +var tableColumnsWorkflow = []table.Column{ + {Title: "Workflow", Width: 32}, + {Title: "File", Width: 48}, +} + +// --------------------------------------------------------------------------- + +var tableColumnsWorkflowHistory = []table.Column{ + {Title: "Workflow", Width: 12}, + {Title: "Commit Message", Width: 16}, + {Title: "Triggered", Width: 12}, + {Title: "Started At", Width: 19}, + {Title: "Status", Width: 9}, + {Title: "Duration", Width: 8}, +} diff --git a/internal/terminal/style/style.go b/internal/terminal/handler/types/style.go similarity index 70% rename from internal/terminal/style/style.go rename to internal/terminal/handler/types/style.go index cc54ecf..72f4347 100644 --- a/internal/terminal/style/style.go +++ b/internal/terminal/handler/types/style.go @@ -1,4 +1,4 @@ -package style +package types import ( "github.com/charmbracelet/lipgloss" @@ -8,12 +8,12 @@ var ( WindowStyleOrange = lipgloss.NewStyle().BorderForeground(lipgloss.Color("#ffaf00")).Border(lipgloss.RoundedBorder()) WindowStyleRed = lipgloss.NewStyle().BorderForeground(lipgloss.Color("9")).Border(lipgloss.RoundedBorder()) WindowStyleGreen = lipgloss.NewStyle().BorderForeground(lipgloss.Color("10")).Border(lipgloss.RoundedBorder()) - WindowStyleGray = lipgloss.NewStyle().BorderForeground(lipgloss.Color("240")).Border(lipgloss.NormalBorder()) - WindowStyleWhite = lipgloss.NewStyle().BorderForeground(lipgloss.Color("255")).Border(lipgloss.NormalBorder()) + WindowStyleGray = lipgloss.NewStyle().BorderForeground(lipgloss.Color("240")).Border(lipgloss.RoundedBorder()) + WindowStyleWhite = lipgloss.NewStyle().BorderForeground(lipgloss.Color("255")).Border(lipgloss.RoundedBorder()) - WindowStyleHelp = WindowStyleGray.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleError = WindowStyleRed.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleProgress = WindowStyleOrange.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleSuccess = WindowStyleGreen.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) - WindowStyleDefault = WindowStyleWhite.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2).Border(lipgloss.RoundedBorder()) + WindowStyleHelp = WindowStyleGray.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) + WindowStyleError = WindowStyleRed.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) + WindowStyleProgress = WindowStyleOrange.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) + WindowStyleSuccess = WindowStyleGreen.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) + WindowStyleDefault = WindowStyleWhite.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) ) From 598ef3d2486dc0bb63180e9b4b67a43dc291a8d6 Mon Sep 17 00:00:00 2001 From: Engin Date: Mon, 2 Sep 2024 23:46:00 +0300 Subject: [PATCH 26/51] Rearrange methods and delete unused code --- internal/terminal/handler/ghinformation.go | 3 - internal/terminal/handler/ghrepository.go | 18 +- internal/terminal/handler/ghtrigger.go | 92 +++++----- .../terminal/handler/ghworkflowhistory.go | 169 +++++++++--------- 4 files changed, 136 insertions(+), 146 deletions(-) diff --git a/internal/terminal/handler/ghinformation.go b/internal/terminal/handler/ghinformation.go index 9be812d..b410312 100644 --- a/internal/terminal/handler/ghinformation.go +++ b/internal/terminal/handler/ghinformation.go @@ -97,9 +97,6 @@ func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case updateSelf: - //if msg.Done { - // return m, nil - //} if msg.RefreshTerminal { m.modelError, cmd = m.modelError.Update(msg) cmds = append(cmds, cmd) diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index 17c1653..6407b7c 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -125,18 +125,6 @@ func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.Us } } -func (m *ModelGithubRepository) SelfUpdater() tea.Cmd { - return func() tea.Msg { - go func() { - select { - case _ = <-m.updateChan: - m.updateChan <- updateSelf{} - } - }() - return <-m.updateChan - } -} - func (m *ModelGithubRepository) Init() tea.Cmd { openInBrowser := func() { m.modelError.SetProgressMessage("Opening in browser...") @@ -230,6 +218,12 @@ func (m *ModelGithubRepository) View() string { return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.viewSearchBar(), m.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) } +func (m *ModelGithubRepository) SelfUpdater() tea.Cmd { + return func() tea.Msg { + return <-m.updateChan + } +} + func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { m.modelError.Reset() // reset previous errors m.modelTabOptions.SetStatus(taboptions.OptionWait) diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index 2a27f2a..95600d2 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -189,6 +189,52 @@ func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } +func (m *ModelGithubTrigger) View() string { + baseStyle := lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).MarginLeft(1) + helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + + if m.triggerFocused { + baseStyle = baseStyle.BorderForeground(lipgloss.Color("240")) + } else { + baseStyle = baseStyle.BorderForeground(lipgloss.Color("#3b698f")) + } + + var tableWidth int + for _, t := range tableColumnsTrigger { + tableWidth += t.Width + } + + newTableColumns := tableColumnsTrigger + widthDiff := m.skeleton.GetTerminalWidth() - tableWidth + if widthDiff > 0 { + keyWidth := &newTableColumns[2].Width + valueWidth := &newTableColumns[4].Width + + *valueWidth += widthDiff - 16 + if *valueWidth%2 == 0 { + *keyWidth = *valueWidth / 2 + } + m.tableTrigger.SetColumns(newTableColumns) + m.tableTrigger.SetHeight(m.skeleton.GetTerminalHeight() - 17) + } + + doc := strings.Builder{} + doc.WriteString(baseStyle.Render(m.tableTrigger.View())) + + var selectedRow = m.tableTrigger.SelectedRow() + var selector = m.emptySelector() + if len(m.tableTrigger.Rows()) > 0 { + if selectedRow[1] == "input" { + selector = m.inputSelector() + } else { + selector = m.optionSelector() + } + } + + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), + lipgloss.JoinHorizontal(lipgloss.Top, selector, m.triggerButton()), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) +} + func (m *ModelGithubTrigger) switchBetweenInputAndTable() { var selectedRow = m.tableTrigger.SelectedRow() @@ -341,52 +387,6 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { } } -func (m *ModelGithubTrigger) View() string { - baseStyle := lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).MarginLeft(1) - helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) - - if m.triggerFocused { - baseStyle = baseStyle.BorderForeground(lipgloss.Color("240")) - } else { - baseStyle = baseStyle.BorderForeground(lipgloss.Color("#3b698f")) - } - - var tableWidth int - for _, t := range tableColumnsTrigger { - tableWidth += t.Width - } - - newTableColumns := tableColumnsTrigger - widthDiff := m.skeleton.GetTerminalWidth() - tableWidth - if widthDiff > 0 { - keyWidth := &newTableColumns[2].Width - valueWidth := &newTableColumns[4].Width - - *valueWidth += widthDiff - 16 - if *valueWidth%2 == 0 { - *keyWidth = *valueWidth / 2 - } - m.tableTrigger.SetColumns(newTableColumns) - m.tableTrigger.SetHeight(m.skeleton.GetTerminalHeight() - 17) - } - - doc := strings.Builder{} - doc.WriteString(baseStyle.Render(m.tableTrigger.View())) - - var selectedRow = m.tableTrigger.SelectedRow() - var selector = m.emptySelector() - if len(m.tableTrigger.Rows()) > 0 { - if selectedRow[1] == "input" { - selector = m.inputSelector() - } else { - selector = m.optionSelector() - } - } - - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), - lipgloss.JoinHorizontal(lipgloss.Top, selector, m.triggerButton()), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) -} - func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { m.modelError.Reset() m.modelError.SetProgressMessage( diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index 7adb49b..c13ac28 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -148,6 +148,90 @@ func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { m.SelfUpdater(), ) } +func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + if m.lastRepository != m.SelectedRepository.RepositoryName { + m.tableReady = false + m.cancelSyncWorkflowHistory() // cancel previous sync + + m.lastRepository = m.SelectedRepository.RepositoryName + + m.syncWorkflowHistoryContext, m.cancelSyncWorkflowHistory = context.WithCancel(context.Background()) + go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) + } + + if m.Workflows != nil { + m.selectedWorkflowID = m.Workflows[m.tableWorkflowHistory.Cursor()].ID + } + + var cmds []tea.Cmd + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.KeyMsg: + switch { + case key.Matches(msg, m.Keys.Refresh): + go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) + case key.Matches(msg, m.Keys.LiveMode): + m.liveMode = !m.liveMode + if m.liveMode { + m.modelError.SetSuccessMessage("Live mode enabled") + m.skeleton.UpdateWidgetValue("live", "Live Mode: On") + } else { + m.modelError.SetSuccessMessage("Live mode disabled") + m.skeleton.UpdateWidgetValue("live", "Live Mode: Off") + } + } + case UpdateWorkflowHistoryMsg: + go func() { + time.Sleep(msg.UpdateAfter) + m.syncWorkflowHistory(m.syncWorkflowHistoryContext) + }() + } + + cmds = append(cmds, m.SelfUpdater()) + + m.modelError, cmd = m.modelError.Update(msg) + cmds = append(cmds, cmd) + + m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) + cmds = append(cmds, cmd) + + m.tableWorkflowHistory, cmd = m.tableWorkflowHistory.Update(msg) + cmds = append(cmds, cmd) + + return m, tea.Batch(cmds...) +} + +func (m *ModelGithubWorkflowHistory) View() string { + helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + + termWidth := m.skeleton.GetTerminalWidth() + termHeight := m.skeleton.GetTerminalHeight() + + var tableWidth int + for _, t := range tableColumnsWorkflowHistory { + tableWidth += t.Width + } + + newTableColumns := tableColumnsWorkflowHistory + widthDiff := termWidth - tableWidth + + if widthDiff > 0 { + if m.updateRound%2 == 0 { + newTableColumns[0].Width += widthDiff - 18 + } else { + newTableColumns[1].Width += widthDiff - 18 + } + m.updateRound++ + m.tableWorkflowHistory.SetColumns(newTableColumns) + } + + m.tableWorkflowHistory.SetHeight(termHeight - 18) + + doc := strings.Builder{} + doc.WriteString(m.tableStyle.Render(m.tableWorkflowHistory.View())) + + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) +} func (m *ModelGithubWorkflowHistory) setupOptions() { openInBrowser := func() { @@ -220,59 +304,6 @@ func (m *ModelGithubWorkflowHistory) setupOptions() { m.modelTabOptions.AddOption("Cancel workflow", cancelWorkflow) } -func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - if m.lastRepository != m.SelectedRepository.RepositoryName { - m.tableReady = false - m.cancelSyncWorkflowHistory() // cancel previous sync - - m.lastRepository = m.SelectedRepository.RepositoryName - - m.syncWorkflowHistoryContext, m.cancelSyncWorkflowHistory = context.WithCancel(context.Background()) - go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) - } - - if m.Workflows != nil { - m.selectedWorkflowID = m.Workflows[m.tableWorkflowHistory.Cursor()].ID - } - - var cmds []tea.Cmd - var cmd tea.Cmd - switch msg := msg.(type) { - case tea.KeyMsg: - switch { - case key.Matches(msg, m.Keys.Refresh): - go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) - case key.Matches(msg, m.Keys.LiveMode): - m.liveMode = !m.liveMode - if m.liveMode { - m.modelError.SetSuccessMessage("Live mode enabled") - m.skeleton.UpdateWidgetValue("live", "Live Mode: On") - } else { - m.modelError.SetSuccessMessage("Live mode disabled") - m.skeleton.UpdateWidgetValue("live", "Live Mode: Off") - } - } - case UpdateWorkflowHistoryMsg: - go func() { - time.Sleep(msg.UpdateAfter) - m.syncWorkflowHistory(m.syncWorkflowHistoryContext) - }() - } - - cmds = append(cmds, m.SelfUpdater()) - - m.modelError, cmd = m.modelError.Update(msg) - cmds = append(cmds, cmd) - - m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) - cmds = append(cmds, cmd) - - m.tableWorkflowHistory, cmd = m.tableWorkflowHistory.Update(msg) - cmds = append(cmds, cmd) - - return m, tea.Batch(cmds...) -} - func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { m.tableReady = false m.modelError.Reset() @@ -326,38 +357,6 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { go m.Update(m) // update model } -func (m *ModelGithubWorkflowHistory) View() string { - helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) - - termWidth := m.skeleton.GetTerminalWidth() - termHeight := m.skeleton.GetTerminalHeight() - - var tableWidth int - for _, t := range tableColumnsWorkflowHistory { - tableWidth += t.Width - } - - newTableColumns := tableColumnsWorkflowHistory - widthDiff := termWidth - tableWidth - - if widthDiff > 0 { - if m.updateRound%2 == 0 { - newTableColumns[0].Width += widthDiff - 18 - } else { - newTableColumns[1].Width += widthDiff - 18 - } - m.updateRound++ - m.tableWorkflowHistory.SetColumns(newTableColumns) - } - - m.tableWorkflowHistory.SetHeight(termHeight - 18) - - doc := strings.Builder{} - doc.WriteString(m.tableStyle.Render(m.tableWorkflowHistory.View())) - - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) -} - func (m *ModelGithubWorkflowHistory) ViewStatus() string { return m.modelError.View() } From 648bcf5605e5e2d39576ef43b291ecbfd55811d4 Mon Sep 17 00:00:00 2001 From: Engin Date: Mon, 2 Sep 2024 23:48:01 +0300 Subject: [PATCH 27/51] Rearrange code --- .../terminal/handler/ghworkflowhistory.go | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index c13ac28..c858852 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -105,37 +105,6 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase } } -type UpdateWorkflowHistoryMsg struct { - UpdateAfter time.Duration -} - -func (m *ModelGithubWorkflowHistory) SelfUpdater() tea.Cmd { - return func() tea.Msg { - select { - case o := <-m.updateSelfChan: - return o - default: - return nil - } - } -} - -func (m *ModelGithubWorkflowHistory) ToggleLiveMode() { - // send UpdateWorkflowHistoryMsg to update the workflow history every 5 seconds with ticker - // send only if liveMode is true - go func() { - t := time.NewTicker(m.liveModeInterval) - for { - select { - case <-t.C: - if m.liveMode { - m.updateSelfChan <- UpdateWorkflowHistoryMsg{UpdateAfter: time.Nanosecond} - } - } - } - }() -} - func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { m.setupOptions() m.ToggleLiveMode() @@ -233,6 +202,37 @@ func (m *ModelGithubWorkflowHistory) View() string { return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) } +type UpdateWorkflowHistoryMsg struct { + UpdateAfter time.Duration +} + +func (m *ModelGithubWorkflowHistory) SelfUpdater() tea.Cmd { + return func() tea.Msg { + select { + case o := <-m.updateSelfChan: + return o + default: + return nil + } + } +} + +func (m *ModelGithubWorkflowHistory) ToggleLiveMode() { + // send UpdateWorkflowHistoryMsg to update the workflow history every 5 seconds with ticker + // send only if liveMode is true + go func() { + t := time.NewTicker(m.liveModeInterval) + for { + select { + case <-t.C: + if m.liveMode { + m.updateSelfChan <- UpdateWorkflowHistoryMsg{UpdateAfter: time.Nanosecond} + } + } + } + }() +} + func (m *ModelGithubWorkflowHistory) setupOptions() { openInBrowser := func() { m.modelError.SetProgressMessage("Opening in browser...") From 371eadae2d0f039ecc5caeb50f7c771cc1bef7cf Mon Sep 17 00:00:00 2001 From: Engin Date: Tue, 3 Sep 2024 00:21:17 +0300 Subject: [PATCH 28/51] Fix refresh history after trigger --- internal/terminal/handler/ghtrigger.go | 8 +-- .../terminal/handler/ghworkflowhistory.go | 62 ++++++++++--------- 2 files changed, 34 insertions(+), 36 deletions(-) diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index 95600d2..92a676d 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -168,9 +168,6 @@ func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "enter", tea.KeyEnter.String(): if m.triggerFocused && m.isTriggerable { go m.triggerWorkflow() - return m, func() tea.Msg { - return UpdateWorkflowHistoryMsg{UpdateAfter: time.Second * 5} - } } } } @@ -484,8 +481,6 @@ func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow contents fetched.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) } - - go m.Update(m) // update model } func (m *ModelGithubTrigger) fillTableWithEmptyMessage() { @@ -614,7 +609,8 @@ func (m *ModelGithubTrigger) triggerWorkflow() { m.optionValues = nil // reset option values m.selectedRepositoryName = "" // reset selected repository name - m.skeleton.SetActivePage("history") // switch tab to workflow history + UpdateWorkflowHistory(time.Second * 5) // update workflow history + m.skeleton.SetActivePage("history") // switch tab to workflow history } func (m *ModelGithubTrigger) emptySelector() string { diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index c858852..cbf83e3 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -50,7 +50,18 @@ type ModelGithubWorkflowHistory struct { modelError *hdlerror.ModelError modelTabOptions *taboptions.Options - updateSelfChan chan UpdateWorkflowHistoryMsg +} + +type workflowHistoryUpdateMsg struct { + UpdateAfter time.Duration +} + +var githubWorkflowHistoryUpdateChan = make(chan workflowHistoryUpdateMsg) + +func UpdateWorkflowHistory(timeAfter time.Duration) { + go func() { + githubWorkflowHistoryUpdateChan <- workflowHistoryUpdateMsg{UpdateAfter: timeAfter} + }() } func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { @@ -87,6 +98,8 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase modelError := hdlerror.SetupModelError(skeleton) tabOptions := taboptions.NewOptions(&modelError) + githubWorkflowHistoryUpdateChan = make(chan workflowHistoryUpdateMsg) + return &ModelGithubWorkflowHistory{ skeleton: skeleton, liveMode: cfg.Settings.LiveMode.Enabled, @@ -100,7 +113,6 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase modelTabOptions: tabOptions, syncWorkflowHistoryContext: context.Background(), cancelSyncWorkflowHistory: func() {}, - updateSelfChan: make(chan UpdateWorkflowHistoryMsg), tableStyle: tableStyle, } } @@ -111,7 +123,7 @@ func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { return tea.Batch( m.modelTabOptions.Init(), func() tea.Msg { - return UpdateWorkflowHistoryMsg{UpdateAfter: time.Second * 1} + return workflowHistoryUpdateMsg{UpdateAfter: time.Second * 1} }, m.modelError.Init(), m.SelfUpdater(), @@ -149,7 +161,7 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.skeleton.UpdateWidgetValue("live", "Live Mode: Off") } } - case UpdateWorkflowHistoryMsg: + case workflowHistoryUpdateMsg: go func() { time.Sleep(msg.UpdateAfter) m.syncWorkflowHistory(m.syncWorkflowHistoryContext) @@ -202,37 +214,12 @@ func (m *ModelGithubWorkflowHistory) View() string { return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) } -type UpdateWorkflowHistoryMsg struct { - UpdateAfter time.Duration -} - func (m *ModelGithubWorkflowHistory) SelfUpdater() tea.Cmd { return func() tea.Msg { - select { - case o := <-m.updateSelfChan: - return o - default: - return nil - } + return <-githubWorkflowHistoryUpdateChan } } -func (m *ModelGithubWorkflowHistory) ToggleLiveMode() { - // send UpdateWorkflowHistoryMsg to update the workflow history every 5 seconds with ticker - // send only if liveMode is true - go func() { - t := time.NewTicker(m.liveModeInterval) - for { - select { - case <-t.C: - if m.liveMode { - m.updateSelfChan <- UpdateWorkflowHistoryMsg{UpdateAfter: time.Nanosecond} - } - } - } - }() -} - func (m *ModelGithubWorkflowHistory) setupOptions() { openInBrowser := func() { m.modelError.SetProgressMessage("Opening in browser...") @@ -304,6 +291,21 @@ func (m *ModelGithubWorkflowHistory) setupOptions() { m.modelTabOptions.AddOption("Cancel workflow", cancelWorkflow) } +func (m *ModelGithubWorkflowHistory) ToggleLiveMode() { + // send UpdateWorkflowHistoryMsg to update the workflow history every 5 seconds with ticker + // send only if liveMode is true + go func() { + t := time.NewTicker(m.liveModeInterval) + for { + select { + case <-t.C: + if m.liveMode { + githubWorkflowHistoryUpdateChan <- workflowHistoryUpdateMsg{UpdateAfter: time.Nanosecond} + } + } + } + }() +} func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { m.tableReady = false m.modelError.Reset() From 87f1532760e035e2f0171d6d3ec0db193a77fa4f Mon Sep 17 00:00:00 2001 From: Engin Date: Thu, 5 Sep 2024 21:50:36 +0300 Subject: [PATCH 29/51] Remove unnecessary spinner and fix self update --- internal/terminal/handler/error/error.go | 81 +------------------ internal/terminal/handler/ghinformation.go | 15 +--- internal/terminal/handler/ghrepository.go | 4 - internal/terminal/handler/ghtrigger.go | 22 ++++- internal/terminal/handler/ghworkflow.go | 26 +++++- .../terminal/handler/ghworkflowhistory.go | 30 +++++-- .../terminal/handler/taboptions/taboptions.go | 4 +- 7 files changed, 69 insertions(+), 113 deletions(-) diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/error/error.go index cbc31e7..3f35e1e 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/error/error.go @@ -2,8 +2,6 @@ package error import ( "fmt" - "github.com/charmbracelet/bubbles/spinner" - tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ts "github.com/termkit/gama/internal/terminal/handler/types" "github.com/termkit/skeleton" @@ -23,12 +21,6 @@ type ModelError struct { // messageType is hold the message type messageType MessageType - - // spinner is hold the spinner - spinner spinner.Model - - updateChan chan UpdateSelf - disableSpinner bool } type UpdateSelf struct { @@ -50,41 +42,11 @@ const ( ) func SetupModelError(skeleton *skeleton.Skeleton) ModelError { - s := spinner.New(spinner.WithSpinner(spinner.Dot)) return ModelError{ - skeleton: skeleton, - spinner: s, - err: nil, - errorMessage: "", - updateChan: make(chan UpdateSelf), - disableSpinner: false, - } -} - -func (m *ModelError) Init() tea.Cmd { - return tea.Batch(m.SelfUpdater()) -} - -func (m *ModelError) Update(msg tea.Msg) (*ModelError, tea.Cmd) { - var cmds []tea.Cmd - var cmd tea.Cmd - - switch msg := msg.(type) { - case spinner.TickMsg: - m.spinner, cmd = m.spinner.Update(msg) - cmds = append(cmds, cmd) - - cmds = append(cmds, m.SelfUpdater()) - case UpdateSelf: - if msg.InProgress { - m.spinner, cmd = m.spinner.Update(m.spinner.Tick()) - cmds = append(cmds, cmd) - } - - cmds = append(cmds, m.SelfUpdater()) + skeleton: skeleton, + err: nil, + errorMessage: "", } - - return m, tea.Batch(cmds...) } func (m *ModelError) View() string { @@ -92,11 +54,6 @@ func (m *ModelError) View() string { width := m.skeleton.GetTerminalWidth() - 4 doc := strings.Builder{} - var s string - if !m.disableSpinner { - s = m.spinner.View() - } - if m.HaveError() { windowStyle = ts.WindowStyleError.Width(width) doc.WriteString(windowStyle.Render(m.viewError())) @@ -106,69 +63,39 @@ func (m *ModelError) View() string { switch m.messageType { case MessageTypeDefault: windowStyle = ts.WindowStyleDefault.Width(width) - s = "" case MessageTypeProgress: windowStyle = ts.WindowStyleProgress.Width(width) case MessageTypeSuccess: windowStyle = ts.WindowStyleSuccess.Width(width) - s = "" default: windowStyle = ts.WindowStyleDefault.Width(width) } - doc.WriteString(windowStyle.Render(lipgloss.JoinHorizontal(lipgloss.Top, m.viewMessage(), " ", s))) + doc.WriteString(windowStyle.Render(m.viewMessage())) return doc.String() } -func (m *ModelError) SelfUpdater() tea.Cmd { - return func() tea.Msg { - return <-m.updateChan - } -} - -func (m *ModelError) EnableSpinner() { - m.disableSpinner = false - //m.updateChan <- UpdateSelf{Message: m.message, InProgress: true} -} - -func (m *ModelError) DisableSpinner() { - m.disableSpinner = true - //m.updateChan <- UpdateSelf{Message: m.message, InProgress: false} -} - func (m *ModelError) SetError(err error) { m.err = err } func (m *ModelError) SetErrorMessage(message string) { m.errorMessage = message - go func() { - m.updateChan <- UpdateSelf{Message: message, InProgress: true} - }() } func (m *ModelError) SetProgressMessage(message string) { m.messageType = MessageTypeProgress m.message = message - go func() { - m.updateChan <- UpdateSelf{Message: message, InProgress: true} - }() } func (m *ModelError) SetSuccessMessage(message string) { m.messageType = MessageTypeSuccess m.message = message - go func() { - m.updateChan <- UpdateSelf{Message: message, InProgress: true} - }() } func (m *ModelError) SetDefaultMessage(message string) { m.messageType = MessageTypeDefault m.message = message - go func() { - m.updateChan <- UpdateSelf{Message: message, InProgress: true} - }() } func (m *ModelError) GetError() error { diff --git a/internal/terminal/handler/ghinformation.go b/internal/terminal/handler/ghinformation.go index b410312..46e4383 100644 --- a/internal/terminal/handler/ghinformation.go +++ b/internal/terminal/handler/ghinformation.go @@ -74,8 +74,7 @@ func (m *ModelInfo) Init() tea.Cmd { go m.checkUpdates(context.Background()) go m.testConnection(context.Background()) - return tea.Batch(tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), - m.modelError.Init(), m.handleSelfUpdate()) + return tea.Batch(tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), m.handleSelfUpdate()) } func (m *ModelInfo) checkUpdates(ctx context.Context) { @@ -94,17 +93,6 @@ func (m *ModelInfo) checkUpdates(ctx context.Context) { func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd - var cmd tea.Cmd - switch msg := msg.(type) { - case updateSelf: - if msg.RefreshTerminal { - m.modelError, cmd = m.modelError.Update(msg) - cmds = append(cmds, cmd) - } - } - - m.modelError, cmd = m.modelError.Update(msg) - cmds = append(cmds, cmd) return m, tea.Batch(cmds...) } @@ -131,7 +119,6 @@ func (m *ModelInfo) View() string { } func (m *ModelInfo) testConnection(ctx context.Context) { - m.modelError.EnableSpinner() m.modelError.SetProgressMessage("Checking your token...") m.skeleton.LockTabs() diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index 6407b7c..5d5d714 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -140,7 +140,6 @@ func (m *ModelGithubRepository) Init() tea.Cmd { } m.modelTabOptions.AddOption("Open in browser", openInBrowser) - m.modelError.Init() go m.syncRepositories(m.syncRepositoriesContext) return tea.Batch(m.modelTabOptions.Init(), m.SelfUpdater()) } @@ -170,9 +169,6 @@ func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, m.SelfUpdater()) } - m.modelError, cmd = m.modelError.Update(msg) - cmds = append(cmds, cmd) - m.textInput, cmd = m.textInput.Update(textInputMsg) cmds = append(cmds, cmd) diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index 92a676d..ada898d 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -50,6 +50,8 @@ type ModelGithubTrigger struct { modelError *hdlerror.ModelError textInput textinput.Model tableTrigger table.Model + + updateSelfChan chan any } func SetupModelGithubTrigger(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubTrigger { @@ -90,11 +92,22 @@ func SetupModelGithubTrigger(skeleton *skeleton.Skeleton, githubUseCase gu.UseCa textInput: ti, syncWorkflowContext: context.Background(), cancelSyncWorkflow: func() {}, + updateSelfChan: make(chan any), + } +} + +func (m *ModelGithubTrigger) selfUpdate() { + m.updateSelfChan <- selfUpdateMsg{} +} + +func (m *ModelGithubTrigger) selfListen() tea.Cmd { + return func() tea.Msg { + return <-m.updateSelfChan } } func (m *ModelGithubTrigger) Init() tea.Cmd { - return tea.Batch(textinput.Blink, m.modelError.Init()) + return tea.Batch(textinput.Blink, m.selfListen()) } func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -170,11 +183,10 @@ func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { go m.triggerWorkflow() } } + case selfUpdateMsg: + cmds = append(cmds, m.selfListen()) } - m.modelError, cmd = m.modelError.Update(msg) - cmds = append(cmds, cmd) - m.tableTrigger, cmd = m.tableTrigger.Update(msg) cmds = append(cmds, cmd) @@ -481,6 +493,8 @@ func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow contents fetched.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) } + + m.selfUpdate() } func (m *ModelGithubTrigger) fillTableWithEmptyMessage() { diff --git a/internal/terminal/handler/ghworkflow.go b/internal/terminal/handler/ghworkflow.go index 8b1f4eb..053d051 100644 --- a/internal/terminal/handler/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow.go @@ -39,6 +39,8 @@ type ModelGithubWorkflow struct { Help help.Model tableTriggerableWorkflow table.Model modelError *hdlerror.ModelError + + updateSelfChan chan any } func SetupModelGithubWorkflow(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflow { @@ -75,17 +77,34 @@ func SetupModelGithubWorkflow(skeleton *skeleton.Skeleton, githubUseCase gu.UseC SelectedRepository: hdltypes.NewSelectedRepository(), syncTriggerableWorkflowsContext: context.Background(), cancelSyncTriggerableWorkflows: func() {}, + updateSelfChan: make(chan any), + } +} + +func (m *ModelGithubWorkflow) selfUpdate() { + m.updateSelfChan <- selfUpdateMsg{} +} + +func (m *ModelGithubWorkflow) selfListen() tea.Cmd { + return func() tea.Msg { + return <-m.updateSelfChan } } func (m *ModelGithubWorkflow) Init() tea.Cmd { - return tea.Batch(m.modelError.Init()) + return tea.Batch(m.selfListen()) } func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd var cmd tea.Cmd + switch msg := msg.(type) { + case selfUpdateMsg: + _ = msg + cmds = append(cmds, m.selfListen()) + } + if m.lastRepository != m.SelectedRepository.RepositoryName { m.tableReady = false // reset table ready status m.cancelSyncTriggerableWorkflows() // cancel previous sync @@ -96,9 +115,6 @@ func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { go m.syncTriggerableWorkflows(m.syncTriggerableWorkflowsContext) } - m.modelError, cmd = m.modelError.Update(msg) - cmds = append(cmds, cmd) - m.tableTriggerableWorkflow, cmd = m.tableTriggerableWorkflow.Update(msg) cmds = append(cmds, cmd) @@ -138,6 +154,8 @@ func (m *ModelGithubWorkflow) View() string { } func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { + defer m.selfUpdate() + m.modelError.Reset() m.modelError.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching triggerable workflows...", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index cbf83e3..3589ad0 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -50,6 +50,8 @@ type ModelGithubWorkflowHistory struct { modelError *hdlerror.ModelError modelTabOptions *taboptions.Options + + updateSelfChan chan any } type workflowHistoryUpdateMsg struct { @@ -114,9 +116,23 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase syncWorkflowHistoryContext: context.Background(), cancelSyncWorkflowHistory: func() {}, tableStyle: tableStyle, + updateSelfChan: make(chan any), + } +} + +func (m *ModelGithubWorkflowHistory) selfUpdate() { + m.updateSelfChan <- selfUpdateMsg{} +} + +func (m *ModelGithubWorkflowHistory) selfListen() tea.Cmd { + return func() tea.Msg { + return <-m.updateSelfChan } } +type selfUpdateMsg struct { +} + func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { m.setupOptions() m.ToggleLiveMode() @@ -125,8 +141,8 @@ func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { func() tea.Msg { return workflowHistoryUpdateMsg{UpdateAfter: time.Second * 1} }, - m.modelError.Init(), m.SelfUpdater(), + m.selfListen(), ) } func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -151,6 +167,7 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch { case key.Matches(msg, m.Keys.Refresh): go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) + cmds = append(cmds, m.SelfUpdater()) case key.Matches(msg, m.Keys.LiveMode): m.liveMode = !m.liveMode if m.liveMode { @@ -166,13 +183,12 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { time.Sleep(msg.UpdateAfter) m.syncWorkflowHistory(m.syncWorkflowHistoryContext) }() + cmds = append(cmds, m.SelfUpdater()) + case selfUpdateMsg: + // do nothing + cmds = append(cmds, m.selfListen()) } - cmds = append(cmds, m.SelfUpdater()) - - m.modelError, cmd = m.modelError.Update(msg) - cmds = append(cmds, cmd) - m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) cmds = append(cmds, cmd) @@ -356,7 +372,7 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { m.tableWorkflowHistory.SetCursor(0) 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 + m.selfUpdate() } func (m *ModelGithubWorkflowHistory) ViewStatus() string { diff --git a/internal/terminal/handler/taboptions/taboptions.go b/internal/terminal/handler/taboptions/taboptions.go index d641d36..8246de2 100644 --- a/internal/terminal/handler/taboptions/taboptions.go +++ b/internal/terminal/handler/taboptions/taboptions.go @@ -79,9 +79,7 @@ func NewOptions(modelError *hdlerror.ModelError) *Options { } func (o *Options) Init() tea.Cmd { - return func() tea.Msg { - return o.modelError.Init() - } + return nil } func (o *Options) Update(msg tea.Msg) (*Options, tea.Cmd) { From 4b01fd0321efde6648017e39fd60ed98c8aff5af Mon Sep 17 00:00:00 2001 From: Engin Date: Thu, 5 Sep 2024 22:10:24 +0300 Subject: [PATCH 30/51] Made listeners common --- internal/terminal/handler/ghinformation.go | 74 +++++++++---------- internal/terminal/handler/ghrepository.go | 22 ++++-- internal/terminal/handler/ghtrigger.go | 2 +- .../terminal/handler/ghworkflowhistory.go | 6 +- internal/terminal/handler/types.go | 4 + 5 files changed, 59 insertions(+), 49 deletions(-) create mode 100644 internal/terminal/handler/types.go diff --git a/internal/terminal/handler/ghinformation.go b/internal/terminal/handler/ghinformation.go index 46e4383..7211747 100644 --- a/internal/terminal/handler/ghinformation.go +++ b/internal/terminal/handler/ghinformation.go @@ -28,12 +28,7 @@ type ModelInfo struct { // keymap Keys githubInformationKeyMap - updateChan chan updateSelf -} - -type updateSelf struct { - RefreshTerminal bool - Done bool + updateSelfChan chan selfUpdateMsg } const ( @@ -67,6 +62,16 @@ func SetupModelInfo(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase, versi } } +func (m *ModelInfo) selfUpdate() { + m.updateSelfChan <- selfUpdateMsg{} +} + +func (m *ModelInfo) selfListen() tea.Cmd { + return func() tea.Msg { + return <-m.updateSelfChan + } +} + func (m *ModelInfo) Init() tea.Cmd { currentVersion = m.version.CurrentVersion() applicationDescription = fmt.Sprintf("Github Actions Manager (%s)", currentVersion) @@ -74,26 +79,18 @@ func (m *ModelInfo) Init() tea.Cmd { go m.checkUpdates(context.Background()) go m.testConnection(context.Background()) - return tea.Batch(tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), m.handleSelfUpdate()) -} - -func (m *ModelInfo) checkUpdates(ctx context.Context) { - isUpdateAvailable, version, err := m.version.IsUpdateAvailable(ctx) - if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("failed to check updates") - newVersionAvailableMsg = fmt.Sprintf("failed to check updates.\nPlease visit: %s", releaseURL) - return - } - - if isUpdateAvailable { - newVersionAvailableMsg = fmt.Sprintf("New version available: %s\nPlease visit: %s", version, releaseURL) - } + return tea.Batch(tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), m.selfListen()) } func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd + switch msg := msg.(type) { + case selfUpdateMsg: + _ = msg + cmds = append(cmds, m.selfListen()) + } + return m, tea.Batch(cmds...) } @@ -118,7 +115,25 @@ func (m *ModelInfo) View() string { return lipgloss.JoinVertical(lipgloss.Center, infoDoc.String(), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) } +func (m *ModelInfo) checkUpdates(ctx context.Context) { + defer m.selfUpdate() + + isUpdateAvailable, version, err := m.version.IsUpdateAvailable(ctx) + if err != nil { + m.modelError.SetError(err) + m.modelError.SetErrorMessage("failed to check updates") + newVersionAvailableMsg = fmt.Sprintf("failed to check updates.\nPlease visit: %s", releaseURL) + return + } + + if isUpdateAvailable { + newVersionAvailableMsg = fmt.Sprintf("New version available: %s\nPlease visit: %s", version, releaseURL) + } +} + func (m *ModelInfo) testConnection(ctx context.Context) { + defer m.selfUpdate() + m.modelError.SetProgressMessage("Checking your token...") m.skeleton.LockTabs() @@ -133,23 +148,6 @@ func (m *ModelInfo) testConnection(ctx context.Context) { m.modelError.Reset() m.modelError.SetSuccessMessage("Welcome to GAMA!") m.skeleton.UnlockTabs() - m.updateChan <- updateSelf{Done: true} -} - -func (m *ModelInfo) handleSelfUpdate() tea.Cmd { - return func() tea.Msg { - go func() { - select { - case o := <-m.updateChan: - if o.Done { - m.updateChan <- updateSelf{Done: true} - } else { - m.updateChan <- updateSelf{RefreshTerminal: true} - } - } - }() - return <-m.updateChan - } } func (m *ModelInfo) ViewStatus() string { diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index 5d5d714..0391f76 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -47,7 +47,7 @@ type ModelGithubRepository struct { textInput textinput.Model - updateChan chan updateSelf + updateSelfChan chan selfUpdateMsg } func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubRepository { @@ -121,7 +121,17 @@ func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.Us textInput: ti, syncRepositoriesContext: context.Background(), cancelSyncRepositories: func() {}, - updateChan: make(chan updateSelf), + updateSelfChan: make(chan selfUpdateMsg), + } +} + +func (m *ModelGithubRepository) selfUpdate() { + m.updateSelfChan <- selfUpdateMsg{} +} + +func (m *ModelGithubRepository) selfListen() tea.Cmd { + return func() tea.Msg { + return <-m.updateSelfChan } } @@ -165,7 +175,7 @@ func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.searchTableGithubRepository.GotoTop() m.searchTableGithubRepository.SetCursor(0) } - case updateSelf: + case selfUpdateMsg: cmds = append(cmds, m.SelfUpdater()) } @@ -216,11 +226,13 @@ func (m *ModelGithubRepository) View() string { func (m *ModelGithubRepository) SelfUpdater() tea.Cmd { return func() tea.Msg { - return <-m.updateChan + return <-m.updateSelfChan } } func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { + defer m.selfUpdate() + m.modelError.Reset() // reset previous errors m.modelTabOptions.SetStatus(taboptions.OptionWait) m.modelError.SetProgressMessage("Fetching repositories...") @@ -268,8 +280,6 @@ func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { //m.updateSearchBarSuggestions() m.textInput.Focus() m.modelError.SetSuccessMessage("Repositories fetched") - - m.updateChan <- updateSelf{} } func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index ada898d..fd10783 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -623,7 +623,7 @@ func (m *ModelGithubTrigger) triggerWorkflow() { m.optionValues = nil // reset option values m.selectedRepositoryName = "" // reset selected repository name - UpdateWorkflowHistory(time.Second * 5) // update workflow history + UpdateWorkflowHistory(time.Second * 3) // update workflow history m.skeleton.SetActivePage("history") // switch tab to workflow history } diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index 3589ad0..2179128 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -130,9 +130,6 @@ func (m *ModelGithubWorkflowHistory) selfListen() tea.Cmd { } } -type selfUpdateMsg struct { -} - func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { m.setupOptions() m.ToggleLiveMode() @@ -323,6 +320,8 @@ func (m *ModelGithubWorkflowHistory) ToggleLiveMode() { }() } func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { + defer m.selfUpdate() + m.tableReady = false m.modelError.Reset() m.modelError.SetProgressMessage( @@ -372,7 +371,6 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { m.tableWorkflowHistory.SetCursor(0) m.modelTabOptions.SetStatus(taboptions.OptionIdle) m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow history fetched.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) - m.selfUpdate() } func (m *ModelGithubWorkflowHistory) ViewStatus() string { diff --git a/internal/terminal/handler/types.go b/internal/terminal/handler/types.go new file mode 100644 index 0000000..1d1e9c3 --- /dev/null +++ b/internal/terminal/handler/types.go @@ -0,0 +1,4 @@ +package handler + +type selfUpdateMsg struct { +} From 39b271bff954947c5bf7e53ea0cde1981ce4fc31 Mon Sep 17 00:00:00 2001 From: Engin Date: Thu, 5 Sep 2024 22:16:40 +0300 Subject: [PATCH 31/51] Minor improvements --- internal/terminal/handler/ghinformation.go | 4 ---- internal/terminal/handler/ghrepository.go | 17 +---------------- internal/terminal/handler/ghtrigger.go | 9 +++------ internal/terminal/handler/ghworkflow.go | 6 +----- internal/terminal/handler/ghworkflowhistory.go | 6 +----- internal/terminal/handler/handler.go | 1 - 6 files changed, 6 insertions(+), 37 deletions(-) diff --git a/internal/terminal/handler/ghinformation.go b/internal/terminal/handler/ghinformation.go index 7211747..aee038e 100644 --- a/internal/terminal/handler/ghinformation.go +++ b/internal/terminal/handler/ghinformation.go @@ -149,7 +149,3 @@ func (m *ModelInfo) testConnection(ctx context.Context) { m.modelError.SetSuccessMessage("Welcome to GAMA!") m.skeleton.UnlockTabs() } - -func (m *ModelInfo) ViewStatus() string { - return m.modelError.View() -} diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index 0391f76..074af44 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -221,7 +221,7 @@ 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.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.viewSearchBar(), m.modelTabOptions.View(), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubRepository) SelfUpdater() tea.Cmd { @@ -319,17 +319,6 @@ func (m *ModelGithubRepository) viewSearchBar() string { return windowStyle.Render(doc.String()) } -func (m *ModelGithubRepository) updateSearchBarSuggestions() { - m.textInput.SetSuggestions([]string{}) - - var suggestions = make([]string, 0, len(m.tableGithubRepository.Rows())) - for _, r := range m.tableGithubRepository.Rows() { - suggestions = append(suggestions, r[0]) - } - - m.textInput.SetSuggestions(suggestions) -} - func (m *ModelGithubRepository) updateTableRowsBySearchBar() { var tableRowsGithubRepository = make([]table.Row, 0, len(m.tableGithubRepository.Rows())) @@ -366,7 +355,3 @@ func (m *ModelGithubRepository) isCharAndSymbol(r []rune) bool { return false } - -func (m *ModelGithubRepository) ViewStatus() string { - return m.modelError.View() -} diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index fd10783..77f51f2 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -397,6 +397,8 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { } func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { + defer m.selfUpdate() + m.modelError.Reset() m.modelError.SetProgressMessage( fmt.Sprintf("[%s@%s] Fetching workflow contents...", @@ -493,8 +495,6 @@ func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow contents fetched.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) } - - m.selfUpdate() } func (m *ModelGithubTrigger) fillTableWithEmptyMessage() { @@ -613,6 +613,7 @@ func (m *ModelGithubTrigger) triggerWorkflow() { m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName, m.selectedWorkflow)) time.Sleep(1 * time.Second) + m.selfUpdate() m.modelError.SetProgressMessage("Switching to workflow history tab...") time.Sleep(1500 * time.Millisecond) @@ -693,7 +694,3 @@ func (m *ModelGithubTrigger) sortTableItemsByName() { }) m.tableTrigger.SetRows(rows) } - -func (m *ModelGithubTrigger) ViewStatus() string { - return m.modelError.View() -} diff --git a/internal/terminal/handler/ghworkflow.go b/internal/terminal/handler/ghworkflow.go index 053d051..5970cc2 100644 --- a/internal/terminal/handler/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow.go @@ -150,7 +150,7 @@ func (m *ModelGithubWorkflow) View() string { doc.WriteString(style.Render(m.tableTriggerableWorkflow.View())) doc.WriteString("\n\n\n") - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { @@ -211,7 +211,3 @@ func (m *ModelGithubWorkflow) handleTableInputs(_ context.Context) { m.SelectedRepository.WorkflowName = selectedRow[1] } } - -func (m *ModelGithubWorkflow) ViewStatus() string { - return m.modelError.View() -} diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index 2179128..3ce38a2 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -224,7 +224,7 @@ func (m *ModelGithubWorkflowHistory) View() string { doc := strings.Builder{} doc.WriteString(m.tableStyle.Render(m.tableWorkflowHistory.View())) - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.ViewStatus(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubWorkflowHistory) SelfUpdater() tea.Cmd { @@ -372,7 +372,3 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { m.modelTabOptions.SetStatus(taboptions.OptionIdle) m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow history fetched.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) } - -func (m *ModelGithubWorkflowHistory) ViewStatus() string { - return m.modelError.View() -} diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index 62a8a0a..adeda9f 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -20,7 +20,6 @@ func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Mod s.SetBorderColor("#ff0055").SetActiveTabBorderColor("#ff0055").SetInactiveTabBorderColor("#82636f").SetWidgetBorderColor("#ff0055") - //s.AddWidget("version", "development mode") time.Sleep(100 * time.Millisecond) s.AddWidget("repositories", "Repository Count: 0") time.Sleep(100 * time.Millisecond) From 65c42cd015be053648f13ef58400c19225cf7ef6 Mon Sep 17 00:00:00 2001 From: Engin Date: Thu, 5 Sep 2024 22:24:41 +0300 Subject: [PATCH 32/51] Rename and make private some variables --- internal/terminal/handler/ghinformation.go | 38 +++---- internal/terminal/handler/ghrepository.go | 50 ++++----- internal/terminal/handler/ghtrigger.go | 86 +++++++-------- internal/terminal/handler/ghworkflow.go | 46 ++++---- .../terminal/handler/ghworkflowhistory.go | 100 +++++++++--------- internal/terminal/handler/keymap.go | 10 +- .../handler/{error => status}/error.go | 38 +++---- .../terminal/handler/taboptions/taboptions.go | 8 +- 8 files changed, 188 insertions(+), 188 deletions(-) rename internal/terminal/handler/{error => status}/error.go (73%) diff --git a/internal/terminal/handler/ghinformation.go b/internal/terminal/handler/ghinformation.go index aee038e..72c991d 100644 --- a/internal/terminal/handler/ghinformation.go +++ b/internal/terminal/handler/ghinformation.go @@ -7,7 +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" + "github.com/termkit/gama/internal/terminal/handler/status" ts "github.com/termkit/gama/internal/terminal/handler/types" pkgversion "github.com/termkit/gama/pkg/version" "github.com/termkit/skeleton" @@ -22,11 +22,11 @@ type ModelInfo struct { github gu.UseCase // models - Help help.Model - modelError *hdlerror.ModelError + help help.Model + status *status.ModelStatus // keymap - Keys githubInformationKeyMap + keys githubInformationKeyMap updateSelfChan chan selfUpdateMsg } @@ -50,15 +50,15 @@ var ( ) func SetupModelInfo(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { - modelError := hdlerror.SetupModelError(skeleton) + modelError := status.SetupModelStatus(skeleton) return &ModelInfo{ - skeleton: skeleton, - github: githubUseCase, - version: version, - Help: help.New(), - Keys: githubInformationKeys, - modelError: &modelError, + skeleton: skeleton, + github: githubUseCase, + version: version, + help: help.New(), + keys: githubInformationKeys, + status: &modelError, } } @@ -112,7 +112,7 @@ func (m *ModelInfo) View() string { infoDoc.WriteString(strings.Repeat("\n", max(0, requiredNewlinesForPadding))) - return lipgloss.JoinVertical(lipgloss.Center, infoDoc.String(), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Center, infoDoc.String(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelInfo) checkUpdates(ctx context.Context) { @@ -120,8 +120,8 @@ func (m *ModelInfo) checkUpdates(ctx context.Context) { isUpdateAvailable, version, err := m.version.IsUpdateAvailable(ctx) if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("failed to check updates") + m.status.SetError(err) + m.status.SetErrorMessage("failed to check updates") newVersionAvailableMsg = fmt.Sprintf("failed to check updates.\nPlease visit: %s", releaseURL) return } @@ -134,18 +134,18 @@ func (m *ModelInfo) checkUpdates(ctx context.Context) { func (m *ModelInfo) testConnection(ctx context.Context) { defer m.selfUpdate() - m.modelError.SetProgressMessage("Checking your token...") + m.status.SetProgressMessage("Checking your token...") m.skeleton.LockTabs() _, err := m.github.GetAuthUser(ctx) if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("failed to test connection, please check your token&permission") + m.status.SetError(err) + m.status.SetErrorMessage("failed to test connection, please check your token&permission") m.skeleton.LockTabs() return } - m.modelError.Reset() - m.modelError.SetSuccessMessage("Welcome to GAMA!") + m.status.Reset() + m.status.SetSuccessMessage("Welcome to GAMA!") m.skeleton.UnlockTabs() } diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index 074af44..8329354 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -12,7 +12,7 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/termkit/gama/internal/github/domain" gu "github.com/termkit/gama/internal/github/usecase" - hdlerror "github.com/termkit/gama/internal/terminal/handler/error" + "github.com/termkit/gama/internal/terminal/handler/status" "github.com/termkit/gama/internal/terminal/handler/taboptions" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" "github.com/termkit/gama/pkg/browser" @@ -29,7 +29,7 @@ type ModelGithubRepository struct { tableReady bool // shared properties - SelectedRepository *hdltypes.SelectedRepository + selectedRepository *hdltypes.SelectedRepository // use cases github gu.UseCase @@ -38,10 +38,10 @@ type ModelGithubRepository struct { Keys githubRepositoryKeyMap // models - Help help.Model + help help.Model tableGithubRepository table.Model searchTableGithubRepository table.Model - modelError *hdlerror.ModelError + status *status.ModelStatus modelTabOptions *taboptions.Options @@ -106,17 +106,17 @@ func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.Us ti.ShowSuggestions = false // disable suggestions, it will be enabled future. // setup models - modelError := hdlerror.SetupModelError(skeleton) + modelError := status.SetupModelStatus(skeleton) tabOptions := taboptions.NewOptions(&modelError) return &ModelGithubRepository{ skeleton: skeleton, - Help: help.New(), + help: help.New(), Keys: githubRepositoryKeys, github: githubUseCase, tableGithubRepository: tableGithubRepository, - modelError: &modelError, - SelectedRepository: hdltypes.NewSelectedRepository(), + status: &modelError, + selectedRepository: hdltypes.NewSelectedRepository(), modelTabOptions: tabOptions, textInput: ti, syncRepositoriesContext: context.Background(), @@ -137,16 +137,16 @@ func (m *ModelGithubRepository) selfListen() tea.Cmd { func (m *ModelGithubRepository) Init() tea.Cmd { openInBrowser := func() { - m.modelError.SetProgressMessage("Opening in browser...") + m.status.SetProgressMessage("Opening in browser...") - err := browser.OpenInBrowser(fmt.Sprintf("https://github.com/%s", m.SelectedRepository.RepositoryName)) + err := browser.OpenInBrowser(fmt.Sprintf("https://github.com/%s", m.selectedRepository.RepositoryName)) if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage(fmt.Sprintf("Cannot open in browser: %v", err)) + m.status.SetError(err) + m.status.SetErrorMessage(fmt.Sprintf("Cannot open in browser: %v", err)) return } - m.modelError.SetSuccessMessage("Opened in browser") + m.status.SetSuccessMessage("Opened in browser") } m.modelTabOptions.AddOption("Open in browser", openInBrowser) @@ -221,7 +221,7 @@ 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.modelTabOptions.View(), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.viewSearchBar(), m.modelTabOptions.View(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubRepository) SelfUpdater() tea.Cmd { @@ -233,9 +233,9 @@ func (m *ModelGithubRepository) SelfUpdater() tea.Cmd { func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { defer m.selfUpdate() - m.modelError.Reset() // reset previous errors + m.status.Reset() // reset previous errors m.modelTabOptions.SetStatus(taboptions.OptionWait) - m.modelError.SetProgressMessage("Fetching repositories...") + m.status.SetProgressMessage("Fetching repositories...") // delete all rows m.tableGithubRepository.SetRows([]table.Row{}) @@ -249,14 +249,14 @@ func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { if errors.Is(err, context.Canceled) { return } else if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Repositories cannot be listed") + m.status.SetError(err) + m.status.SetErrorMessage("Repositories cannot be listed") return } if len(repositories.Repositories) == 0 { m.modelTabOptions.SetStatus(taboptions.OptionNone) - m.modelError.SetDefaultMessage("No repositories found") + m.status.SetDefaultMessage("No repositories found") m.textInput.Blur() return } @@ -279,7 +279,7 @@ func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { m.tableReady = true //m.updateSearchBarSuggestions() m.textInput.Focus() - m.modelError.SetSuccessMessage("Repositories fetched") + m.status.SetSuccessMessage("Repositories fetched") } func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { @@ -292,8 +292,8 @@ func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { // Synchronize selected repository name with parent model if len(selectedRow) > 0 && selectedRow[0] != "" { - m.SelectedRepository.RepositoryName = selectedRow[0] - m.SelectedRepository.BranchName = selectedRow[1] + m.selectedRepository.RepositoryName = selectedRow[0] + m.selectedRepository.BranchName = selectedRow[1] } m.modelTabOptions.SetStatus(taboptions.OptionIdle) @@ -329,9 +329,9 @@ func (m *ModelGithubRepository) updateTableRowsBySearchBar() { } if len(tableRowsGithubRepository) == 0 { - m.SelectedRepository.RepositoryName = "" - m.SelectedRepository.BranchName = "" - m.SelectedRepository.WorkflowName = "" + m.selectedRepository.RepositoryName = "" + m.selectedRepository.BranchName = "" + m.selectedRepository.WorkflowName = "" } m.tableGithubRepository.SetRows(tableRowsGithubRepository) diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index 77f51f2..c00702a 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -10,7 +10,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" + "github.com/termkit/gama/internal/terminal/handler/status" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" "github.com/termkit/gama/pkg/workflow" "github.com/termkit/skeleton" @@ -37,7 +37,7 @@ type ModelGithubTrigger struct { triggerFocused bool // shared properties - SelectedRepository *hdltypes.SelectedRepository + selectedRepository *hdltypes.SelectedRepository // use cases github gu.UseCase @@ -46,8 +46,8 @@ type ModelGithubTrigger struct { Keys githubTriggerKeyMap // models - Help help.Model - modelError *hdlerror.ModelError + help help.Model + status *status.ModelStatus textInput textinput.Model tableTrigger table.Model @@ -80,14 +80,14 @@ func SetupModelGithubTrigger(skeleton *skeleton.Skeleton, githubUseCase gu.UseCa ti.Blur() ti.CharLimit = 72 - modelError := hdlerror.SetupModelError(skeleton) + modelError := status.SetupModelStatus(skeleton) return &ModelGithubTrigger{ skeleton: skeleton, - Help: help.New(), + help: help.New(), Keys: githubTriggerKeys, github: githubUseCase, - SelectedRepository: hdltypes.NewSelectedRepository(), - modelError: &modelError, + selectedRepository: hdltypes.NewSelectedRepository(), + status: &modelError, tableTrigger: tableTrigger, textInput: ti, syncWorkflowContext: context.Background(), @@ -111,22 +111,22 @@ func (m *ModelGithubTrigger) Init() tea.Cmd { } func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - if m.SelectedRepository.WorkflowName == "" { - m.modelError.Reset() - m.modelError.SetDefaultMessage("No workflow selected.") + if m.selectedRepository.WorkflowName == "" { + m.status.Reset() + m.status.SetDefaultMessage("No workflow selected.") m.fillTableWithEmptyMessage() return m, nil } - if m.SelectedRepository.WorkflowName != "" && (m.SelectedRepository.WorkflowName != m.selectedWorkflow || m.SelectedRepository.RepositoryName != m.selectedRepositoryName) { + if m.selectedRepository.WorkflowName != "" && (m.selectedRepository.WorkflowName != m.selectedWorkflow || m.selectedRepository.RepositoryName != m.selectedRepositoryName) { m.tableReady = false m.isTriggerable = false m.triggerFocused = false m.cancelSyncWorkflow() // cancel previous sync workflow - m.selectedWorkflow = m.SelectedRepository.WorkflowName - m.selectedRepositoryName = m.SelectedRepository.RepositoryName + m.selectedWorkflow = m.selectedRepository.WorkflowName + m.selectedRepositoryName = m.selectedRepository.RepositoryName m.syncWorkflowContext, m.cancelSyncWorkflow = context.WithCancel(context.Background()) go m.syncWorkflowContent(m.syncWorkflowContext) @@ -241,7 +241,7 @@ func (m *ModelGithubTrigger) View() string { } return lipgloss.JoinVertical(lipgloss.Top, doc.String(), - lipgloss.JoinHorizontal(lipgloss.Top, selector, m.triggerButton()), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) + lipgloss.JoinHorizontal(lipgloss.Top, selector, m.triggerButton()), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubTrigger) switchBetweenInputAndTable() { @@ -399,30 +399,30 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { defer m.selfUpdate() - m.modelError.Reset() - m.modelError.SetProgressMessage( + m.status.Reset() + m.status.SetProgressMessage( fmt.Sprintf("[%s@%s] Fetching workflow contents...", - m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) // reset table rows m.tableTrigger.SetRows([]table.Row{}) workflowContent, err := m.github.InspectWorkflow(ctx, gu.InspectWorkflowInput{ - Repository: m.SelectedRepository.RepositoryName, - Branch: m.SelectedRepository.BranchName, + Repository: m.selectedRepository.RepositoryName, + Branch: m.selectedRepository.BranchName, WorkflowFile: m.selectedWorkflow, }) if errors.Is(err, context.Canceled) { return } else if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Workflow contents cannot be fetched") + m.status.SetError(err) + m.status.SetErrorMessage("Workflow contents cannot be fetched") return } if workflowContent.Workflow == nil { - m.modelError.SetError(errors.New("workflow contents cannot be empty")) - m.modelError.SetErrorMessage("You have no workflow contents") + m.status.SetError(errors.New("workflow contents cannot be empty")) + m.status.SetErrorMessage("You have no workflow contents") return } @@ -489,11 +489,11 @@ func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { len(workflowContent.Workflow.Choices) == 0 && len(workflowContent.Workflow.Inputs) == 0 { m.fillTableWithEmptyMessage() - m.modelError.SetDefaultMessage(fmt.Sprintf("[%s@%s] Workflow doesn't contain options but still triggerable", - m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) + m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] Workflow doesn't contain options but still triggerable", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) } else { - m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow contents fetched.", - m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) + m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow contents fetched.", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) } } @@ -513,7 +513,7 @@ func (m *ModelGithubTrigger) fillTableWithEmptyMessage() { func (m *ModelGithubTrigger) showInformationIfAnyEmptyValue() { for _, row := range m.tableTrigger.Rows() { if row[4] == "" { - m.modelError.SetDefaultMessage("Info: You have empty values. These values uses their default values.") + m.status.SetDefaultMessage("Info: You have empty values. These values uses their default values.") return } } @@ -537,8 +537,8 @@ func (m *ModelGithubTrigger) triggerButton() string { func (m *ModelGithubTrigger) fillEmptyValuesWithDefault() { if m.workflowContent == nil { - m.modelError.SetError(errors.New("workflow contents cannot be empty")) - m.modelError.SetErrorMessage("You have no workflow contents") + m.status.SetError(errors.New("workflow contents cannot be empty")) + m.status.SetErrorMessage("You have no workflow contents") return } @@ -581,40 +581,40 @@ func (m *ModelGithubTrigger) triggerWorkflow() { m.fillEmptyValuesWithDefault() } - m.modelError.SetProgressMessage( + m.status.SetProgressMessage( fmt.Sprintf("[%s@%s]:[%s] Triggering workflow...", - m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName, m.selectedWorkflow)) + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName, m.selectedWorkflow)) if m.workflowContent == nil { - m.modelError.SetErrorMessage("Workflow contents cannot be empty") + m.status.SetErrorMessage("Workflow contents cannot be empty") return } content, err := m.workflowContent.ToJson() if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Workflow contents cannot be converted to JSON") + m.status.SetError(err) + m.status.SetErrorMessage("Workflow contents cannot be converted to JSON") return } _, err = m.github.TriggerWorkflow(context.Background(), gu.TriggerWorkflowInput{ - Repository: m.SelectedRepository.RepositoryName, - Branch: m.SelectedRepository.BranchName, + Repository: m.selectedRepository.RepositoryName, + Branch: m.selectedRepository.BranchName, WorkflowFile: m.selectedWorkflow, Content: content, }) if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Workflow cannot be triggered") + m.status.SetError(err) + m.status.SetErrorMessage("Workflow cannot be triggered") return } - m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s]:[%s] Workflow triggered.", - m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName, m.selectedWorkflow)) + m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s]:[%s] Workflow triggered.", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName, m.selectedWorkflow)) time.Sleep(1 * time.Second) m.selfUpdate() - m.modelError.SetProgressMessage("Switching to workflow history tab...") + m.status.SetProgressMessage("Switching to workflow history tab...") time.Sleep(1500 * time.Millisecond) // move these operations under new function named "resetTabSettings" diff --git a/internal/terminal/handler/ghworkflow.go b/internal/terminal/handler/ghworkflow.go index 5970cc2..0918fec 100644 --- a/internal/terminal/handler/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow.go @@ -10,7 +10,7 @@ import ( "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/table" - hdlerror "github.com/termkit/gama/internal/terminal/handler/error" + "github.com/termkit/gama/internal/terminal/handler/status" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" tea "github.com/charmbracelet/bubbletea" @@ -27,18 +27,18 @@ type ModelGithubWorkflow struct { lastRepository string // shared properties - SelectedRepository *hdltypes.SelectedRepository + selectedRepository *hdltypes.SelectedRepository // use cases github gu.UseCase // keymap - Keys githubWorkflowKeyMap + keys githubWorkflowKeyMap // models - Help help.Model + help help.Model tableTriggerableWorkflow table.Model - modelError *hdlerror.ModelError + status *status.ModelStatus updateSelfChan chan any } @@ -65,16 +65,16 @@ func SetupModelGithubWorkflow(skeleton *skeleton.Skeleton, githubUseCase gu.UseC Bold(false) tableTriggerableWorkflow.SetStyles(s) - modelError := hdlerror.SetupModelError(skeleton) + modelError := status.SetupModelStatus(skeleton) return &ModelGithubWorkflow{ skeleton: skeleton, - Help: help.New(), - Keys: githubWorkflowKeys, + help: help.New(), + keys: githubWorkflowKeys, github: githubUseCase, - modelError: &modelError, + status: &modelError, tableTriggerableWorkflow: tableTriggerableWorkflow, - SelectedRepository: hdltypes.NewSelectedRepository(), + selectedRepository: hdltypes.NewSelectedRepository(), syncTriggerableWorkflowsContext: context.Background(), cancelSyncTriggerableWorkflows: func() {}, updateSelfChan: make(chan any), @@ -105,12 +105,12 @@ func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, m.selfListen()) } - if m.lastRepository != m.SelectedRepository.RepositoryName { + if m.lastRepository != m.selectedRepository.RepositoryName { m.tableReady = false // reset table ready status m.cancelSyncTriggerableWorkflows() // cancel previous sync m.syncTriggerableWorkflowsContext, m.cancelSyncTriggerableWorkflows = context.WithCancel(context.Background()) - m.lastRepository = m.SelectedRepository.RepositoryName + m.lastRepository = m.selectedRepository.RepositoryName go m.syncTriggerableWorkflows(m.syncTriggerableWorkflowsContext) } @@ -150,33 +150,33 @@ func (m *ModelGithubWorkflow) View() string { doc.WriteString(style.Render(m.tableTriggerableWorkflow.View())) doc.WriteString("\n\n\n") - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { defer m.selfUpdate() - m.modelError.Reset() - m.modelError.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching triggerable workflows...", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) + m.status.Reset() + m.status.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching triggerable workflows...", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) // delete all rows m.tableTriggerableWorkflow.SetRows([]table.Row{}) triggerableWorkflows, err := m.github.GetTriggerableWorkflows(ctx, gu.GetTriggerableWorkflowsInput{ - Repository: m.SelectedRepository.RepositoryName, - Branch: m.SelectedRepository.BranchName, + Repository: m.selectedRepository.RepositoryName, + Branch: m.selectedRepository.BranchName, }) if errors.Is(err, context.Canceled) { return } else if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Triggerable workflows cannot be listed") + m.status.SetError(err) + m.status.SetErrorMessage("Triggerable workflows cannot be listed") return } if len(triggerableWorkflows.TriggerableWorkflows) == 0 { - m.SelectedRepository.WorkflowName = "" - m.modelError.SetDefaultMessage(fmt.Sprintf("[%s@%s] No triggerable workflow found.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) + m.selectedRepository.WorkflowName = "" + m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] No triggerable workflow found.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) return } @@ -195,7 +195,7 @@ func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { m.tableTriggerableWorkflow.SetRows(tableRowsTriggerableWorkflow) m.tableReady = true - m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s] Triggerable workflows fetched.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) + m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s] Triggerable workflows fetched.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) } func (m *ModelGithubWorkflow) handleTableInputs(_ context.Context) { @@ -208,6 +208,6 @@ func (m *ModelGithubWorkflow) handleTableInputs(_ context.Context) { selectedRow := m.tableTriggerableWorkflow.SelectedRow() if len(rows) > 0 && len(selectedRow) > 0 { - m.SelectedRepository.WorkflowName = selectedRow[1] + m.selectedRepository.WorkflowName = selectedRow[1] } } diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index 3ce38a2..c45059f 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -15,7 +15,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" + "github.com/termkit/gama/internal/terminal/handler/status" "github.com/termkit/gama/internal/terminal/handler/taboptions" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" "github.com/termkit/gama/pkg/browser" @@ -33,21 +33,21 @@ type ModelGithubWorkflowHistory struct { lastRepository string syncWorkflowHistoryContext context.Context cancelSyncWorkflowHistory context.CancelFunc - Workflows []gu.Workflow + workflows []gu.Workflow // shared properties - SelectedRepository *hdltypes.SelectedRepository + selectedRepository *hdltypes.SelectedRepository // use cases github gu.UseCase // keymap - Keys githubWorkflowHistoryKeyMap + keys githubWorkflowHistoryKeyMap // models Help help.Model tableWorkflowHistory table.Model - modelError *hdlerror.ModelError + status *status.ModelStatus modelTabOptions *taboptions.Options @@ -97,7 +97,7 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) - modelError := hdlerror.SetupModelError(skeleton) + modelError := status.SetupModelStatus(skeleton) tabOptions := taboptions.NewOptions(&modelError) githubWorkflowHistoryUpdateChan = make(chan workflowHistoryUpdateMsg) @@ -107,11 +107,11 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase liveMode: cfg.Settings.LiveMode.Enabled, liveModeInterval: cfg.Settings.LiveMode.Interval, Help: help.New(), - Keys: githubWorkflowHistoryKeys, + keys: githubWorkflowHistoryKeys, github: githubUseCase, tableWorkflowHistory: tableWorkflowHistory, - modelError: &modelError, - SelectedRepository: hdltypes.NewSelectedRepository(), + status: &modelError, + selectedRepository: hdltypes.NewSelectedRepository(), modelTabOptions: tabOptions, syncWorkflowHistoryContext: context.Background(), cancelSyncWorkflowHistory: func() {}, @@ -143,18 +143,18 @@ func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { ) } func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - if m.lastRepository != m.SelectedRepository.RepositoryName { + if m.lastRepository != m.selectedRepository.RepositoryName { m.tableReady = false m.cancelSyncWorkflowHistory() // cancel previous sync - m.lastRepository = m.SelectedRepository.RepositoryName + m.lastRepository = m.selectedRepository.RepositoryName m.syncWorkflowHistoryContext, m.cancelSyncWorkflowHistory = context.WithCancel(context.Background()) go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) } - if m.Workflows != nil { - m.selectedWorkflowID = m.Workflows[m.tableWorkflowHistory.Cursor()].ID + if m.workflows != nil { + m.selectedWorkflowID = m.workflows[m.tableWorkflowHistory.Cursor()].ID } var cmds []tea.Cmd @@ -162,16 +162,16 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch { - case key.Matches(msg, m.Keys.Refresh): + case key.Matches(msg, m.keys.Refresh): go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) cmds = append(cmds, m.SelfUpdater()) - case key.Matches(msg, m.Keys.LiveMode): + case key.Matches(msg, m.keys.LiveMode): m.liveMode = !m.liveMode if m.liveMode { - m.modelError.SetSuccessMessage("Live mode enabled") + m.status.SetSuccessMessage("Live mode enabled") m.skeleton.UpdateWidgetValue("live", "Live Mode: On") } else { - m.modelError.SetSuccessMessage("Live mode disabled") + m.status.SetSuccessMessage("Live mode disabled") m.skeleton.UpdateWidgetValue("live", "Live Mode: Off") } } @@ -224,7 +224,7 @@ func (m *ModelGithubWorkflowHistory) View() string { doc := strings.Builder{} doc.WriteString(m.tableStyle.Render(m.tableWorkflowHistory.View())) - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.modelError.View(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubWorkflowHistory) SelfUpdater() tea.Cmd { @@ -235,68 +235,68 @@ func (m *ModelGithubWorkflowHistory) SelfUpdater() tea.Cmd { func (m *ModelGithubWorkflowHistory) setupOptions() { openInBrowser := func() { - m.modelError.SetProgressMessage("Opening in browser...") + m.status.SetProgressMessage("Opening in browser...") - var selectedWorkflow = fmt.Sprintf("https://github.com/%s/actions/runs/%d", m.SelectedRepository.RepositoryName, m.selectedWorkflowID) + var selectedWorkflow = fmt.Sprintf("https://github.com/%s/actions/runs/%d", m.selectedRepository.RepositoryName, m.selectedWorkflowID) err := browser.OpenInBrowser(selectedWorkflow) if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Failed to open in browser") + m.status.SetError(err) + m.status.SetErrorMessage("Failed to open in browser") return } - m.modelError.SetSuccessMessage("Opened in browser") + m.status.SetSuccessMessage("Opened in browser") } reRunFailedJobs := func() { - m.modelError.SetProgressMessage("Re-running failed jobs...") + m.status.SetProgressMessage("Re-running failed jobs...") _, err := m.github.ReRunFailedJobs(context.Background(), gu.ReRunFailedJobsInput{ - Repository: m.SelectedRepository.RepositoryName, + Repository: m.selectedRepository.RepositoryName, WorkflowID: m.selectedWorkflowID, }) if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Failed to re-run failed jobs") + m.status.SetError(err) + m.status.SetErrorMessage("Failed to re-run failed jobs") return } - m.modelError.SetSuccessMessage("Re-ran failed jobs") + m.status.SetSuccessMessage("Re-ran failed jobs") } reRunWorkflow := func() { - m.modelError.SetProgressMessage("Re-running workflow...") + m.status.SetProgressMessage("Re-running workflow...") _, err := m.github.ReRunWorkflow(context.Background(), gu.ReRunWorkflowInput{ - Repository: m.SelectedRepository.RepositoryName, + Repository: m.selectedRepository.RepositoryName, WorkflowID: m.selectedWorkflowID, }) if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Failed to re-run workflow") + m.status.SetError(err) + m.status.SetErrorMessage("Failed to re-run workflow") return } - m.modelError.SetSuccessMessage("Re-ran workflow") + m.status.SetSuccessMessage("Re-ran workflow") } cancelWorkflow := func() { - m.modelError.SetProgressMessage("Canceling workflow...") + m.status.SetProgressMessage("Canceling workflow...") _, err := m.github.CancelWorkflow(context.Background(), gu.CancelWorkflowInput{ - Repository: m.SelectedRepository.RepositoryName, + Repository: m.selectedRepository.RepositoryName, WorkflowID: m.selectedWorkflowID, }) if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Failed to cancel workflow") + m.status.SetError(err) + m.status.SetErrorMessage("Failed to cancel workflow") return } - m.modelError.SetSuccessMessage("Canceled workflow") + m.status.SetSuccessMessage("Canceled workflow") } m.modelTabOptions.AddOption("Open in browser", openInBrowser) m.modelTabOptions.AddOption("Rerun failed jobs", reRunFailedJobs) @@ -323,39 +323,39 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { defer m.selfUpdate() m.tableReady = false - m.modelError.Reset() - m.modelError.SetProgressMessage( - fmt.Sprintf("[%s@%s] Fetching workflow history...", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) + m.status.Reset() + m.status.SetProgressMessage( + fmt.Sprintf("[%s@%s] Fetching workflow history...", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) m.modelTabOptions.SetStatus(taboptions.OptionWait) // delete all rows m.tableWorkflowHistory.SetRows([]table.Row{}) // delete old workflows - m.Workflows = nil + m.workflows = nil workflowHistory, err := m.github.GetWorkflowHistory(ctx, gu.GetWorkflowHistoryInput{ - Repository: m.SelectedRepository.RepositoryName, - Branch: m.SelectedRepository.BranchName, + Repository: m.selectedRepository.RepositoryName, + Branch: m.selectedRepository.BranchName, }) if errors.Is(err, context.Canceled) { return } else if err != nil { - m.modelError.SetError(err) - m.modelError.SetErrorMessage("Workflow history cannot be listed") + m.status.SetError(err) + m.status.SetErrorMessage("Workflow history cannot be listed") return } if len(workflowHistory.Workflows) == 0 { m.modelTabOptions.SetStatus(taboptions.OptionNone) - m.modelError.SetDefaultMessage(fmt.Sprintf("[%s@%s] No workflows found.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) + m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] No workflows found.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) return } - m.Workflows = workflowHistory.Workflows + m.workflows = workflowHistory.Workflows var tableRowsWorkflowHistory []table.Row - for _, workflowRun := range m.Workflows { + for _, workflowRun := range m.workflows { tableRowsWorkflowHistory = append(tableRowsWorkflowHistory, table.Row{ workflowRun.WorkflowName, workflowRun.ActionName, @@ -370,5 +370,5 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { m.tableWorkflowHistory.SetRows(tableRowsWorkflowHistory) m.tableWorkflowHistory.SetCursor(0) m.modelTabOptions.SetStatus(taboptions.OptionIdle) - m.modelError.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow history fetched.", m.SelectedRepository.RepositoryName, m.SelectedRepository.BranchName)) + m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow history fetched.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) } diff --git a/internal/terminal/handler/keymap.go b/internal/terminal/handler/keymap.go index 14fa331..b2781e0 100644 --- a/internal/terminal/handler/keymap.go +++ b/internal/terminal/handler/keymap.go @@ -75,7 +75,7 @@ var githubInformationKeys = func() githubInformationKeyMap { }() func (m *ModelInfo) ViewHelp() string { - return m.Help.View(m.Keys) + return m.help.View(m.keys) } // --------------------------------------------------------------------------- @@ -120,7 +120,7 @@ var githubRepositoryKeys = func() githubRepositoryKeyMap { }() func (m *ModelGithubRepository) ViewHelp() string { - return m.Help.View(m.Keys) + return m.help.View(m.Keys) } // --------------------------------------------------------------------------- @@ -171,7 +171,7 @@ var githubWorkflowHistoryKeys = func() githubWorkflowHistoryKeyMap { }() func (m *ModelGithubWorkflowHistory) ViewHelp() string { - return m.Help.View(m.Keys) + return m.Help.View(m.keys) } // --------------------------------------------------------------------------- @@ -204,7 +204,7 @@ var githubWorkflowKeys = func() githubWorkflowKeyMap { }() func (m *ModelGithubWorkflow) ViewHelp() string { - return m.Help.View(m.Keys) + return m.help.View(m.keys) } // --------------------------------------------------------------------------- @@ -255,5 +255,5 @@ var githubTriggerKeys = func() githubTriggerKeyMap { }() func (m *ModelGithubTrigger) ViewHelp() string { - return m.Help.View(m.Keys) + return m.help.View(m.Keys) } diff --git a/internal/terminal/handler/error/error.go b/internal/terminal/handler/status/error.go similarity index 73% rename from internal/terminal/handler/error/error.go rename to internal/terminal/handler/status/error.go index 3f35e1e..1ab8147 100644 --- a/internal/terminal/handler/error/error.go +++ b/internal/terminal/handler/status/error.go @@ -1,4 +1,4 @@ -package error +package status import ( "fmt" @@ -8,7 +8,7 @@ import ( "strings" ) -type ModelError struct { +type ModelStatus struct { skeleton *skeleton.Skeleton // err is hold the error err error @@ -41,15 +41,15 @@ const ( MessageTypeSuccess MessageType = "success" ) -func SetupModelError(skeleton *skeleton.Skeleton) ModelError { - return ModelError{ +func SetupModelStatus(skeleton *skeleton.Skeleton) ModelStatus { + return ModelStatus{ skeleton: skeleton, err: nil, errorMessage: "", } } -func (m *ModelError) View() string { +func (m *ModelStatus) View() string { var windowStyle = lipgloss.NewStyle().BorderStyle(lipgloss.RoundedBorder()) width := m.skeleton.GetTerminalWidth() - 4 doc := strings.Builder{} @@ -75,66 +75,66 @@ func (m *ModelError) View() string { return doc.String() } -func (m *ModelError) SetError(err error) { +func (m *ModelStatus) SetError(err error) { m.err = err } -func (m *ModelError) SetErrorMessage(message string) { +func (m *ModelStatus) SetErrorMessage(message string) { m.errorMessage = message } -func (m *ModelError) SetProgressMessage(message string) { +func (m *ModelStatus) SetProgressMessage(message string) { m.messageType = MessageTypeProgress m.message = message } -func (m *ModelError) SetSuccessMessage(message string) { +func (m *ModelStatus) SetSuccessMessage(message string) { m.messageType = MessageTypeSuccess m.message = message } -func (m *ModelError) SetDefaultMessage(message string) { +func (m *ModelStatus) SetDefaultMessage(message string) { m.messageType = MessageTypeDefault m.message = message } -func (m *ModelError) GetError() error { +func (m *ModelStatus) GetError() error { return m.err } -func (m *ModelError) GetErrorMessage() string { +func (m *ModelStatus) GetErrorMessage() string { return m.errorMessage } -func (m *ModelError) GetMessage() string { +func (m *ModelStatus) GetMessage() string { return m.message } -func (m *ModelError) ResetError() { +func (m *ModelStatus) ResetError() { m.err = nil m.errorMessage = "" } -func (m *ModelError) ResetMessage() { +func (m *ModelStatus) ResetMessage() { m.message = "" } -func (m *ModelError) Reset() { +func (m *ModelStatus) Reset() { m.ResetError() m.ResetMessage() } -func (m *ModelError) HaveError() bool { +func (m *ModelStatus) HaveError() bool { return m.err != nil } -func (m *ModelError) viewError() string { +func (m *ModelStatus) viewError() string { doc := strings.Builder{} doc.WriteString(fmt.Sprintf("Error [%v]: %s", m.err, m.errorMessage)) return doc.String() } -func (m *ModelError) viewMessage() string { +func (m *ModelStatus) viewMessage() string { doc := strings.Builder{} doc.WriteString(m.message) return doc.String() diff --git a/internal/terminal/handler/taboptions/taboptions.go b/internal/terminal/handler/taboptions/taboptions.go index 8246de2..c79b6b0 100644 --- a/internal/terminal/handler/taboptions/taboptions.go +++ b/internal/terminal/handler/taboptions/taboptions.go @@ -7,14 +7,14 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - hdlerror "github.com/termkit/gama/internal/terminal/handler/error" + hdlerror "github.com/termkit/gama/internal/terminal/handler/status" ) type Options struct { Style lipgloss.Style - modelError *hdlerror.ModelError - previousModelError hdlerror.ModelError + modelError *hdlerror.ModelStatus + previousModelError hdlerror.ModelStatus modelLock bool status OptionStatus @@ -48,7 +48,7 @@ func (o OptionStatus) String() string { return string(o) } -func NewOptions(modelError *hdlerror.ModelError) *Options { +func NewOptions(modelError *hdlerror.ModelStatus) *Options { var b = lipgloss.RoundedBorder() b.Right = "├" b.Left = "┤" From 99f11872757c6fdab94842f22d8e172f1ed6dd71 Mon Sep 17 00:00:00 2001 From: Engin Date: Sat, 7 Sep 2024 12:57:52 +0300 Subject: [PATCH 33/51] Rename & minor improvements --- internal/terminal/handler/ghinformation.go | 56 +++++++++---------- internal/terminal/handler/ghrepository.go | 21 +++---- internal/terminal/handler/ghtrigger.go | 27 +++------ internal/terminal/handler/ghworkflow.go | 4 +- .../terminal/handler/ghworkflowhistory.go | 14 ++--- .../terminal/handler/taboptions/taboptions.go | 30 +++++----- internal/terminal/handler/types.go | 1 + 7 files changed, 66 insertions(+), 87 deletions(-) diff --git a/internal/terminal/handler/ghinformation.go b/internal/terminal/handler/ghinformation.go index 72c991d..8a99149 100644 --- a/internal/terminal/handler/ghinformation.go +++ b/internal/terminal/handler/ghinformation.go @@ -16,7 +16,13 @@ import ( type ModelInfo struct { skeleton *skeleton.Skeleton - version pkgversion.Version + + logo string + releaseURL string + newVersionAvailableMsg string + applicationDescription string + + version pkgversion.Version // use cases github gu.UseCase @@ -31,34 +37,30 @@ type ModelInfo struct { updateSelfChan chan selfUpdateMsg } -const ( - releaseURL = "https://github.com/termkit/gama/releases" +func SetupModelInfo(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { + modelStatus := status.SetupModelStatus(skeleton) + + const ( + releaseURL = "https://github.com/termkit/gama/releases" - applicationName = ` + applicationName = ` ..|'''.| | '|| ||' | .|' ' ||| ||| ||| ||| || .... | || |'|..'|| | || '|. || .''''|. | '|' || .''''|. ''|...'| .|. .||. .|. | .||. .|. .||. ` -) - -var ( - currentVersion string - newVersionAvailableMsg string - applicationDescription string -) - -func SetupModelInfo(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { - modelError := status.SetupModelStatus(skeleton) + ) return &ModelInfo{ - skeleton: skeleton, - github: githubUseCase, - version: version, - help: help.New(), - keys: githubInformationKeys, - status: &modelError, + skeleton: skeleton, + releaseURL: releaseURL, + logo: applicationName, + github: githubUseCase, + version: version, + help: help.New(), + keys: githubInformationKeys, + status: &modelStatus, } } @@ -73,8 +75,7 @@ func (m *ModelInfo) selfListen() tea.Cmd { } func (m *ModelInfo) Init() tea.Cmd { - currentVersion = m.version.CurrentVersion() - applicationDescription = fmt.Sprintf("Github Actions Manager (%s)", currentVersion) + m.applicationDescription = fmt.Sprintf("Github Actions Manager (%s)", m.version.CurrentVersion()) go m.checkUpdates(context.Background()) go m.testConnection(context.Background()) @@ -103,12 +104,11 @@ func (m *ModelInfo) View() string { if requiredNewLinesForCenter < 0 { requiredNewLinesForCenter = 0 } - infoDoc.WriteString(strings.Repeat("\n", requiredNewLinesForCenter)) - infoDoc.WriteString(lipgloss.JoinVertical(lipgloss.Center, applicationName, applicationDescription, newVersionAvailableMsg)) + infoDoc.WriteString(strings.Repeat("\n", requiredNewLinesForCenter)) + infoDoc.WriteString(lipgloss.JoinVertical(lipgloss.Center, m.logo, m.applicationDescription, m.newVersionAvailableMsg)) - docHeight := lipgloss.Height(infoDoc.String()) - requiredNewlinesForPadding := m.skeleton.GetTerminalHeight() - docHeight - 12 + requiredNewlinesForPadding := m.skeleton.GetTerminalHeight() - lipgloss.Height(infoDoc.String()) - 12 infoDoc.WriteString(strings.Repeat("\n", max(0, requiredNewlinesForPadding))) @@ -122,12 +122,12 @@ func (m *ModelInfo) checkUpdates(ctx context.Context) { if err != nil { m.status.SetError(err) m.status.SetErrorMessage("failed to check updates") - newVersionAvailableMsg = fmt.Sprintf("failed to check updates.\nPlease visit: %s", releaseURL) + m.newVersionAvailableMsg = fmt.Sprintf("failed to check updates.\nPlease visit: %s", m.releaseURL) return } if isUpdateAvailable { - newVersionAvailableMsg = fmt.Sprintf("New version available: %s\nPlease visit: %s", version, releaseURL) + m.newVersionAvailableMsg = fmt.Sprintf("New version available: %s\nPlease visit: %s", version, m.releaseURL) } } diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index 8329354..bd569d2 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -106,8 +106,8 @@ func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.Us ti.ShowSuggestions = false // disable suggestions, it will be enabled future. // setup models - modelError := status.SetupModelStatus(skeleton) - tabOptions := taboptions.NewOptions(&modelError) + modelStatus := status.SetupModelStatus(skeleton) + tabOptions := taboptions.NewOptions(&modelStatus) return &ModelGithubRepository{ skeleton: skeleton, @@ -115,7 +115,7 @@ func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.Us Keys: githubRepositoryKeys, github: githubUseCase, tableGithubRepository: tableGithubRepository, - status: &modelError, + status: &modelStatus, selectedRepository: hdltypes.NewSelectedRepository(), modelTabOptions: tabOptions, textInput: ti, @@ -218,10 +218,9 @@ func (m *ModelGithubRepository) View() string { m.tableGithubRepository.SetHeight(m.skeleton.GetTerminalHeight() - 20) } - doc := strings.Builder{} - doc.WriteString(baseStyle.Render(m.tableGithubRepository.View())) - - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.viewSearchBar(), m.modelTabOptions.View(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Top, + baseStyle.Render(m.tableGithubRepository.View()), m.viewSearchBar(), + m.modelTabOptions.View(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubRepository) SelfUpdater() tea.Cmd { @@ -277,7 +276,6 @@ func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { m.searchTableGithubRepository.SetCursor(0) m.tableReady = true - //m.updateSearchBarSuggestions() m.textInput.Focus() m.status.SetSuccessMessage("Repositories fetched") } @@ -307,16 +305,11 @@ func (m *ModelGithubRepository) viewSearchBar() string { Padding(0, 1). Width(m.skeleton.GetTerminalWidth() - 6).MarginLeft(1) - // Build the options list - doc := strings.Builder{} - if len(m.textInput.Value()) > 0 { windowStyle = windowStyle.BorderForeground(lipgloss.Color("39")) } - doc.WriteString(m.textInput.View()) - - return windowStyle.Render(doc.String()) + return windowStyle.Render(m.textInput.View()) } func (m *ModelGithubRepository) updateTableRowsBySearchBar() { diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index c00702a..771c2e7 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -80,14 +80,14 @@ func SetupModelGithubTrigger(skeleton *skeleton.Skeleton, githubUseCase gu.UseCa ti.Blur() ti.CharLimit = 72 - modelError := status.SetupModelStatus(skeleton) + modelStatus := status.SetupModelStatus(skeleton) return &ModelGithubTrigger{ skeleton: skeleton, help: help.New(), Keys: githubTriggerKeys, github: githubUseCase, selectedRepository: hdltypes.NewSelectedRepository(), - status: &modelError, + status: &modelStatus, tableTrigger: tableTrigger, textInput: ti, syncWorkflowContext: context.Background(), @@ -227,9 +227,6 @@ func (m *ModelGithubTrigger) View() string { m.tableTrigger.SetHeight(m.skeleton.GetTerminalHeight() - 17) } - doc := strings.Builder{} - doc.WriteString(baseStyle.Render(m.tableTrigger.View())) - var selectedRow = m.tableTrigger.SelectedRow() var selector = m.emptySelector() if len(m.tableTrigger.Rows()) > 0 { @@ -240,8 +237,9 @@ func (m *ModelGithubTrigger) View() string { } } - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), - lipgloss.JoinHorizontal(lipgloss.Top, selector, m.triggerButton()), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Top, + baseStyle.Render(m.tableTrigger.View()), lipgloss.JoinHorizontal(lipgloss.Top, selector, m.triggerButton()), + m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubTrigger) switchBetweenInputAndTable() { @@ -636,10 +634,7 @@ func (m *ModelGithubTrigger) emptySelector() string { Padding(0, 1). Width(m.skeleton.GetTerminalWidth() - 18).MarginLeft(1) - // Build the options list - doc := strings.Builder{} - - return windowStyle.Render(doc.String()) + return windowStyle.Render("") } func (m *ModelGithubTrigger) inputSelector() string { @@ -667,9 +662,6 @@ func (m *ModelGithubTrigger) optionSelector() string { selectedOptionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("120")).Padding(0, 1) unselectedOptionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("140")).Padding(0, 1) - // Build the options list - doc := strings.Builder{} - var processedValues []string for i, option := range m.optionValues { if i == m.optionCursor { @@ -679,12 +671,7 @@ func (m *ModelGithubTrigger) optionSelector() string { } } - horizontal := lipgloss.JoinHorizontal(lipgloss.Left, processedValues...) - - doc.WriteString(horizontal) - - // Apply window style to the entire list - return windowStyle.Render(doc.String()) + return windowStyle.Render(lipgloss.JoinHorizontal(lipgloss.Left, processedValues...)) } func (m *ModelGithubTrigger) sortTableItemsByName() { diff --git a/internal/terminal/handler/ghworkflow.go b/internal/terminal/handler/ghworkflow.go index 0918fec..90007be 100644 --- a/internal/terminal/handler/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow.go @@ -65,14 +65,14 @@ func SetupModelGithubWorkflow(skeleton *skeleton.Skeleton, githubUseCase gu.UseC Bold(false) tableTriggerableWorkflow.SetStyles(s) - modelError := status.SetupModelStatus(skeleton) + modelStatus := status.SetupModelStatus(skeleton) return &ModelGithubWorkflow{ skeleton: skeleton, help: help.New(), keys: githubWorkflowKeys, github: githubUseCase, - status: &modelError, + status: &modelStatus, tableTriggerableWorkflow: tableTriggerableWorkflow, selectedRepository: hdltypes.NewSelectedRepository(), syncTriggerableWorkflowsContext: context.Background(), diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index c45059f..462d504 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/termkit/gama/internal/config" "github.com/termkit/skeleton" - "strings" "time" "github.com/charmbracelet/bubbles/help" @@ -97,8 +96,8 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) - modelError := status.SetupModelStatus(skeleton) - tabOptions := taboptions.NewOptions(&modelError) + modelStatus := status.SetupModelStatus(skeleton) + tabOptions := taboptions.NewOptions(&modelStatus) githubWorkflowHistoryUpdateChan = make(chan workflowHistoryUpdateMsg) @@ -110,7 +109,7 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase keys: githubWorkflowHistoryKeys, github: githubUseCase, tableWorkflowHistory: tableWorkflowHistory, - status: &modelError, + status: &modelStatus, selectedRepository: hdltypes.NewSelectedRepository(), modelTabOptions: tabOptions, syncWorkflowHistoryContext: context.Background(), @@ -221,10 +220,9 @@ func (m *ModelGithubWorkflowHistory) View() string { m.tableWorkflowHistory.SetHeight(termHeight - 18) - doc := strings.Builder{} - doc.WriteString(m.tableStyle.Render(m.tableWorkflowHistory.View())) - - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.modelTabOptions.View(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) + return lipgloss.JoinVertical(lipgloss.Top, + m.tableStyle.Render(m.tableWorkflowHistory.View()), m.modelTabOptions.View(), + m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } func (m *ModelGithubWorkflowHistory) SelfUpdater() tea.Cmd { diff --git a/internal/terminal/handler/taboptions/taboptions.go b/internal/terminal/handler/taboptions/taboptions.go index c79b6b0..038e0c5 100644 --- a/internal/terminal/handler/taboptions/taboptions.go +++ b/internal/terminal/handler/taboptions/taboptions.go @@ -7,17 +7,17 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - hdlerror "github.com/termkit/gama/internal/terminal/handler/status" + "github.com/termkit/gama/internal/terminal/handler/status" ) type Options struct { Style lipgloss.Style - modelError *hdlerror.ModelStatus - previousModelError hdlerror.ModelStatus - modelLock bool + status *status.ModelStatus + previousStatus status.ModelStatus + modelLock bool - status OptionStatus + optionStatus OptionStatus options []string optionsAction []string @@ -48,7 +48,7 @@ func (o OptionStatus) String() string { return string(o) } -func NewOptions(modelError *hdlerror.ModelStatus) *Options { +func NewOptions(modelStatus *status.ModelStatus) *Options { var b = lipgloss.RoundedBorder() b.Right = "├" b.Left = "┤" @@ -73,8 +73,8 @@ func NewOptions(modelError *hdlerror.ModelStatus) *Options { options: initialOptions, optionsAction: initialOptionsAction, optionsWithFunc: optionsWithFunc, - status: OptionWait, - modelError: modelError, + optionStatus: OptionWait, + status: modelStatus, } } @@ -85,7 +85,7 @@ func (o *Options) Init() tea.Cmd { func (o *Options) Update(msg tea.Msg) (*Options, tea.Cmd) { var cmd tea.Cmd - if o.status == OptionWait || o.status == OptionNone { + if o.optionStatus == OptionWait || o.optionStatus == OptionNone { return o, cmd } @@ -115,7 +115,7 @@ func (o *Options) View() string { opts = append(opts, " ") for i, option := range o.optionsAction { - switch o.status { + switch o.optionStatus { case OptionWait: style = style.BorderForeground(lipgloss.Color("208")) case OptionNone: @@ -162,7 +162,7 @@ func (o *Options) updateCursor(cursor int) { } func (o *Options) SetStatus(status OptionStatus) { - o.status = status + o.optionStatus = status o.options[0] = status.String() o.optionsAction[0] = status.String() } @@ -184,18 +184,18 @@ func (o *Options) getOptionMessage() string { func (o *Options) showAreYouSure() { if !o.modelLock { - o.previousModelError = *o.modelError + o.previousStatus = *o.status o.modelLock = true } - o.modelError.Reset() - o.modelError.SetProgressMessage(fmt.Sprintf("Are you sure you want to %s?", o.getOptionMessage())) + o.status.Reset() + o.status.SetProgressMessage(fmt.Sprintf("Are you sure you want to %s?", o.getOptionMessage())) } func (o *Options) switchToPreviousError() { if o.modelLock { return } - *o.modelError = o.previousModelError + *o.status = o.previousStatus } func (o *Options) executeOption() { diff --git a/internal/terminal/handler/types.go b/internal/terminal/handler/types.go index 1d1e9c3..c51a3d2 100644 --- a/internal/terminal/handler/types.go +++ b/internal/terminal/handler/types.go @@ -1,4 +1,5 @@ package handler +// selfUpdateMsg is a message to trigger update & view type selfUpdateMsg struct { } From a9110ab4fcf4e46a7048709fc7d5fef56aa064c6 Mon Sep 17 00:00:00 2001 From: Engin Date: Sat, 7 Sep 2024 13:08:48 +0300 Subject: [PATCH 34/51] Minor improvements --- internal/github/repository/repository.go | 14 -------------- internal/github/usecase/usecase.go | 9 +++++---- pkg/workflow/workflow.go | 2 +- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/internal/github/repository/repository.go b/internal/github/repository/repository.go index 646c5c2..454bd32 100644 --- a/internal/github/repository/repository.go +++ b/internal/github/repository/repository.go @@ -261,20 +261,6 @@ func (r *Repo) InspectWorkflowContent(ctx context.Context, repository string, br return decodedContent, nil } -//func (r *Repo) GetWorkflowRun(ctx context.Context, repository string, runID int64) (GithubWorkflowRun, error) { -// // Get a workflow run for the given repository and runID -// var workflowRun GithubWorkflowRun -// err := r.do(ctx, nil, &workflowRun, requestOptions{ -// method: http.MethodGet, -// path: []string{"repos",repository,"actions","runs",strconv.FormatInt(runID, 10)}, -// }) -// if err != nil { -// return GithubWorkflowRun{}, err -// } -// -// return workflowRun, nil -//} - func (r *Repo) getWorkflowFile(ctx context.Context, repository string, path string) (string, error) { // Get the content of the workflow file var githubFile githubFile diff --git a/internal/github/usecase/usecase.go b/internal/github/usecase/usecase.go index 15c521d..2c78943 100644 --- a/internal/github/usecase/usecase.go +++ b/internal/github/usecase/usecase.go @@ -220,16 +220,17 @@ func (u useCase) getDuration(startTime time.Time, endTime time.Time, status stri var diff time.Duration if status != "completed" { - diff = time.Now().Sub(localEndTime) + diff = time.Since(localStartTime) } else { diff = localEndTime.Sub(localStartTime) } - if diff.Seconds() < 60 { + switch { + case diff.Seconds() < 60: return fmt.Sprintf("%ds", int(diff.Seconds())) - } else if diff.Seconds() < 3600 { + case diff.Seconds() < 3600: return fmt.Sprintf("%dm %ds", int(diff.Minutes()), int(diff.Seconds())%60) - } else { + default: return fmt.Sprintf("%dh %dm %ds", int(diff.Hours()), int(diff.Minutes())%60, int(diff.Seconds())%60) } } diff --git a/pkg/workflow/workflow.go b/pkg/workflow/workflow.go index 8eed977..72f4957 100644 --- a/pkg/workflow/workflow.go +++ b/pkg/workflow/workflow.go @@ -55,7 +55,7 @@ func ParseWorkflow(content py.WorkflowContent) (*Workflow, error) { } for key, value := range content.On.WorkflowDispatch.Inputs { - if value.JSONContent != nil && len(value.JSONContent) > 0 { + if len(value.JSONContent) > 0 { var keyValue []KeyValue for k, v := range value.JSONContent { keyValue = append(keyValue, KeyValue{ From 206ba46f04a62db00a1679f898c2c89315342097 Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 18 Dec 2024 00:06:02 +0300 Subject: [PATCH 35/51] Update gitignore --- .gitignore | 3 ++- internal/terminal/handler/status/{error.go => status.go} | 0 2 files changed, 2 insertions(+), 1 deletion(-) rename internal/terminal/handler/status/{error.go => status.go} (100%) diff --git a/.gitignore b/.gitignore index 8355cdf..a58caf4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ vendor/ # Go workspace file go.work +go.work.sum # ide data .idea @@ -30,4 +31,4 @@ release/ # binary main -gama \ No newline at end of file +gama diff --git a/internal/terminal/handler/status/error.go b/internal/terminal/handler/status/status.go similarity index 100% rename from internal/terminal/handler/status/error.go rename to internal/terminal/handler/status/status.go From a9bc5efe37ea7d463986d564108e2dc6eb0c51cc Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 18 Dec 2024 01:39:41 +0300 Subject: [PATCH 36/51] Upgrade dependencies --- go.mod | 43 +++++++++++++++-------------- go.sum | 87 ++++++++++++++++++++++++---------------------------------- 2 files changed, 59 insertions(+), 71 deletions(-) diff --git a/go.mod b/go.mod index 2313b28..1a757cf 100644 --- a/go.mod +++ b/go.mod @@ -1,29 +1,33 @@ module github.com/termkit/gama -go 1.22.6 +go 1.23 + +replace github.com/termkit/skeleton => ../skeleton require ( - github.com/Masterminds/semver/v3 v3.3.0 - github.com/charmbracelet/bubbles v0.19.0 - github.com/charmbracelet/bubbletea v1.1.0 - github.com/charmbracelet/lipgloss v0.13.0 + github.com/Masterminds/semver/v3 v3.3.1 + github.com/charmbracelet/bubbles v0.20.0 + github.com/charmbracelet/bubbletea v1.2.4 + github.com/charmbracelet/lipgloss v1.0.0 github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.9.0 - github.com/termkit/skeleton v0.1.2 + github.com/stretchr/testify v1.10.0 + //github.com/termkit/skeleton v0.1.2 gopkg.in/yaml.v3 v3.0.1 ) +require github.com/termkit/skeleton v0.0.0-00010101000000-000000000000 + require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/x/ansi v0.2.3 // indirect - github.com/charmbracelet/x/term v0.2.0 // indirect + github.com/charmbracelet/x/ansi v0.6.0 // indirect + github.com/charmbracelet/x/term v0.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/magiconair/properties v1.8.7 // indirect + github.com/magiconair/properties v1.8.9 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect @@ -31,21 +35,20 @@ require ( github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.15.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/locafero v0.6.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index dd0dbee..9b2a1ca 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,23 @@ -github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= -github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= -github.com/charmbracelet/bubbles v0.19.0 h1:gKZkKXPP6GlDk6EcfujDK19PCQqRjaJZQ7QRERx1UF0= -github.com/charmbracelet/bubbles v0.19.0/go.mod h1:WILteEqZ+krG5c3ntGEMeG99nCupcuIk7V0/zOP0tOA= -github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c= -github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4= -github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= -github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= -github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY= -github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= +github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= +github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE= +github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM= +github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= +github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= +github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA= +github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q= github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b h1:MnAMdlwSltxJyULnrYbkZpp4k58Co7Tah3ciKhSNo0Q= github.com/charmbracelet/x/exp/golden v0.0.0-20240815200342-61de596daa2b/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= -github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= -github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= +github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -26,10 +25,10 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -38,8 +37,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= @@ -54,9 +53,8 @@ github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELU github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -64,53 +62,40 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= +github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/termkit/skeleton v0.1.2 h1:8bQD1w0lHI1VF/13JE2Udg65HN3UUcRPJdIvwzLvFq8= -github.com/termkit/skeleton v0.1.2/go.mod h1:guH2iOGt7SkjmvwFytqDVHFv3yh5ZiGv7B0HpuNW4ME= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= -golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= +golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= -golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From e11dbd3714bdad04ed2aadcb86876a98770a4fa4 Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 18 Dec 2024 01:41:57 +0300 Subject: [PATCH 37/51] Remove "Launch the selected option" helper. No need to show it always. --- internal/terminal/handler/keymap.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/internal/terminal/handler/keymap.go b/internal/terminal/handler/keymap.go index b2781e0..6b832f3 100644 --- a/internal/terminal/handler/keymap.go +++ b/internal/terminal/handler/keymap.go @@ -82,19 +82,17 @@ func (m *ModelInfo) ViewHelp() string { type githubRepositoryKeyMap struct { Refresh teakey.Binding - LaunchTab teakey.Binding SwitchTab teakey.Binding } func (k githubRepositoryKeyMap) ShortHelp() []teakey.Binding { - return []teakey.Binding{k.SwitchTab, k.Refresh, k.LaunchTab} + return []teakey.Binding{k.SwitchTab, k.Refresh} } func (k githubRepositoryKeyMap) FullHelp() [][]teakey.Binding { return [][]teakey.Binding{ {k.SwitchTab}, {k.Refresh}, - {k.LaunchTab}, } } @@ -108,10 +106,6 @@ var githubRepositoryKeys = func() githubRepositoryKeyMap { teakey.WithKeys(cfg.Shortcuts.Refresh), teakey.WithHelp(cfg.Shortcuts.Refresh, "Refresh list"), ), - LaunchTab: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Enter), - teakey.WithHelp(cfg.Shortcuts.Enter, "Launch the selected option"), - ), SwitchTab: teakey.NewBinding( teakey.WithKeys(""), // help-only binding teakey.WithHelp(tabSwitch, "switch tab"), @@ -126,21 +120,19 @@ func (m *ModelGithubRepository) ViewHelp() string { // --------------------------------------------------------------------------- type githubWorkflowHistoryKeyMap struct { - LaunchTab teakey.Binding Refresh teakey.Binding SwitchTab teakey.Binding LiveMode teakey.Binding } func (k githubWorkflowHistoryKeyMap) ShortHelp() []teakey.Binding { - return []teakey.Binding{k.SwitchTab, k.Refresh, k.LaunchTab, k.LiveMode} + return []teakey.Binding{k.SwitchTab, k.Refresh, k.LiveMode} } func (k githubWorkflowHistoryKeyMap) FullHelp() [][]teakey.Binding { return [][]teakey.Binding{ {k.SwitchTab}, {k.Refresh}, - {k.LaunchTab}, {k.LiveMode}, } } @@ -155,10 +147,6 @@ var githubWorkflowHistoryKeys = func() githubWorkflowHistoryKeyMap { teakey.WithKeys(cfg.Shortcuts.Refresh), teakey.WithHelp(cfg.Shortcuts.Refresh, "Refresh list"), ), - LaunchTab: teakey.NewBinding( - teakey.WithKeys(cfg.Shortcuts.Enter), - teakey.WithHelp(cfg.Shortcuts.Enter, "Launch the selected option"), - ), LiveMode: teakey.NewBinding( teakey.WithKeys(cfg.Shortcuts.LiveMode), teakey.WithHelp(cfg.Shortcuts.LiveMode, "Toggle live mode"), From 8efeab63a475b1daa9e70c77986e0e83bda71143 Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 18 Dec 2024 01:43:26 +0300 Subject: [PATCH 38/51] Make confirmation message better --- internal/terminal/handler/taboptions/taboptions.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/terminal/handler/taboptions/taboptions.go b/internal/terminal/handler/taboptions/taboptions.go index 038e0c5..3733ab5 100644 --- a/internal/terminal/handler/taboptions/taboptions.go +++ b/internal/terminal/handler/taboptions/taboptions.go @@ -183,12 +183,19 @@ func (o *Options) getOptionMessage() string { } func (o *Options) showAreYouSure() { + var yellowStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("4")).Blink(true) + if !o.modelLock { o.previousStatus = *o.status o.modelLock = true } o.status.Reset() - o.status.SetProgressMessage(fmt.Sprintf("Are you sure you want to %s?", o.getOptionMessage())) + o.status.SetProgressMessage(fmt.Sprintf( + "Are you sure you want to %s? %s", + o.getOptionMessage(), + yellowStyle.Render("[ Press Enter ]"), + )) + } func (o *Options) switchToPreviousError() { From 1460ed0aeba7da0f53d8bff0e40cbf422263b22d Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 18 Dec 2024 01:44:39 +0300 Subject: [PATCH 39/51] Improve updater mechanism, remove obsolete and unused code for better maintainability --- internal/terminal/handler/ghinformation.go | 41 +++++------- internal/terminal/handler/ghrepository.go | 44 ++++--------- internal/terminal/handler/ghtrigger.go | 39 ++++-------- internal/terminal/handler/ghworkflow.go | 29 ++------- .../terminal/handler/ghworkflowhistory.go | 63 ++++--------------- internal/terminal/handler/handler.go | 27 +++++--- internal/terminal/handler/status/status.go | 5 -- .../terminal/handler/taboptions/taboptions.go | 7 ++- internal/terminal/handler/types.go | 4 -- 9 files changed, 79 insertions(+), 180 deletions(-) diff --git a/internal/terminal/handler/ghinformation.go b/internal/terminal/handler/ghinformation.go index 8a99149..e2f03ae 100644 --- a/internal/terminal/handler/ghinformation.go +++ b/internal/terminal/handler/ghinformation.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" gu "github.com/termkit/gama/internal/github/usecase" @@ -33,12 +34,10 @@ type ModelInfo struct { // keymap keys githubInformationKeyMap - - updateSelfChan chan selfUpdateMsg } -func SetupModelInfo(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { - modelStatus := status.SetupModelStatus(skeleton) +func SetupModelInfo(sk *skeleton.Skeleton, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { + modelStatus := status.SetupModelStatus(sk) const ( releaseURL = "https://github.com/termkit/gama/releases" @@ -53,7 +52,7 @@ func SetupModelInfo(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase, versi ) return &ModelInfo{ - skeleton: skeleton, + skeleton: sk, releaseURL: releaseURL, logo: applicationName, github: githubUseCase, @@ -64,35 +63,25 @@ func SetupModelInfo(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase, versi } } -func (m *ModelInfo) selfUpdate() { - m.updateSelfChan <- selfUpdateMsg{} -} - -func (m *ModelInfo) selfListen() tea.Cmd { - return func() tea.Msg { - return <-m.updateSelfChan - } -} - func (m *ModelInfo) Init() tea.Cmd { m.applicationDescription = fmt.Sprintf("Github Actions Manager (%s)", m.version.CurrentVersion()) go m.checkUpdates(context.Background()) go m.testConnection(context.Background()) - return tea.Batch(tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), m.selfListen()) + return tea.Batch(tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)")) } func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmds []tea.Cmd - switch msg := msg.(type) { - case selfUpdateMsg: - _ = msg - cmds = append(cmds, m.selfListen()) + case tea.KeyMsg: + switch { + case key.Matches(msg, m.keys.Quit): + return m, tea.Quit + } } - return m, tea.Batch(cmds...) + return m, nil } func (m *ModelInfo) View() string { @@ -112,11 +101,13 @@ func (m *ModelInfo) View() string { infoDoc.WriteString(strings.Repeat("\n", max(0, requiredNewlinesForPadding))) - return lipgloss.JoinVertical(lipgloss.Center, infoDoc.String(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) + str := lipgloss.JoinVertical(lipgloss.Center, infoDoc.String(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) + + return str } func (m *ModelInfo) checkUpdates(ctx context.Context) { - defer m.selfUpdate() + defer m.skeleton.TriggerUpdate() isUpdateAvailable, version, err := m.version.IsUpdateAvailable(ctx) if err != nil { @@ -132,7 +123,7 @@ func (m *ModelInfo) checkUpdates(ctx context.Context) { } func (m *ModelInfo) testConnection(ctx context.Context) { - defer m.selfUpdate() + defer m.skeleton.TriggerUpdate() m.status.SetProgressMessage("Checking your token...") m.skeleton.LockTabs() diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index bd569d2..4b9de66 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -46,11 +46,9 @@ type ModelGithubRepository struct { modelTabOptions *taboptions.Options textInput textinput.Model - - updateSelfChan chan selfUpdateMsg } -func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubRepository { +func SetupModelGithubRepository(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubRepository { var tableRowsGithubRepository []table.Row tableGithubRepository := table.New( @@ -101,16 +99,16 @@ func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.Us ti := textinput.New() ti.Blur() - ti.CharLimit = 72 + ti.CharLimit = 128 ti.Placeholder = "Type to search repository" ti.ShowSuggestions = false // disable suggestions, it will be enabled future. // setup models - modelStatus := status.SetupModelStatus(skeleton) - tabOptions := taboptions.NewOptions(&modelStatus) + modelStatus := status.SetupModelStatus(sk) + tabOptions := taboptions.NewOptions(sk, &modelStatus) return &ModelGithubRepository{ - skeleton: skeleton, + skeleton: sk, help: help.New(), Keys: githubRepositoryKeys, github: githubUseCase, @@ -121,17 +119,6 @@ func SetupModelGithubRepository(skeleton *skeleton.Skeleton, githubUseCase gu.Us textInput: ti, syncRepositoriesContext: context.Background(), cancelSyncRepositories: func() {}, - updateSelfChan: make(chan selfUpdateMsg), - } -} - -func (m *ModelGithubRepository) selfUpdate() { - m.updateSelfChan <- selfUpdateMsg{} -} - -func (m *ModelGithubRepository) selfListen() tea.Cmd { - return func() tea.Msg { - return <-m.updateSelfChan } } @@ -150,8 +137,10 @@ func (m *ModelGithubRepository) Init() tea.Cmd { } m.modelTabOptions.AddOption("Open in browser", openInBrowser) + go m.syncRepositories(m.syncRepositoriesContext) - return tea.Batch(m.modelTabOptions.Init(), m.SelfUpdater()) + + return tea.Batch(m.modelTabOptions.Init()) } func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -175,8 +164,6 @@ func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.searchTableGithubRepository.GotoTop() m.searchTableGithubRepository.SetCursor(0) } - case selfUpdateMsg: - cmds = append(cmds, m.SelfUpdater()) } m.textInput, cmd = m.textInput.Update(textInputMsg) @@ -190,11 +177,11 @@ func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.searchTableGithubRepository, cmd = m.searchTableGithubRepository.Update(msg) cmds = append(cmds, cmd) + m.handleTableInputs(m.syncRepositoriesContext) + m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) cmds = append(cmds, cmd) - m.handleTableInputs(m.syncRepositoriesContext) - return m, tea.Batch(cmds...) } @@ -223,14 +210,9 @@ func (m *ModelGithubRepository) View() string { m.modelTabOptions.View(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } -func (m *ModelGithubRepository) SelfUpdater() tea.Cmd { - return func() tea.Msg { - return <-m.updateSelfChan - } -} - func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { - defer m.selfUpdate() + defer m.skeleton.TriggerUpdate() + defer m.modelTabOptions.SetStatus(taboptions.OptionIdle) m.status.Reset() // reset previous errors m.modelTabOptions.SetStatus(taboptions.OptionWait) @@ -293,8 +275,6 @@ func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { 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.go b/internal/terminal/handler/ghtrigger.go index 771c2e7..d94e333 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -50,11 +50,9 @@ type ModelGithubTrigger struct { status *status.ModelStatus textInput textinput.Model tableTrigger table.Model - - updateSelfChan chan any } -func SetupModelGithubTrigger(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubTrigger { +func SetupModelGithubTrigger(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubTrigger { var tableRowsTrigger []table.Row tableTrigger := table.New( @@ -80,9 +78,9 @@ func SetupModelGithubTrigger(skeleton *skeleton.Skeleton, githubUseCase gu.UseCa ti.Blur() ti.CharLimit = 72 - modelStatus := status.SetupModelStatus(skeleton) + modelStatus := status.SetupModelStatus(sk) return &ModelGithubTrigger{ - skeleton: skeleton, + skeleton: sk, help: help.New(), Keys: githubTriggerKeys, github: githubUseCase, @@ -92,22 +90,11 @@ func SetupModelGithubTrigger(skeleton *skeleton.Skeleton, githubUseCase gu.UseCa textInput: ti, syncWorkflowContext: context.Background(), cancelSyncWorkflow: func() {}, - updateSelfChan: make(chan any), - } -} - -func (m *ModelGithubTrigger) selfUpdate() { - m.updateSelfChan <- selfUpdateMsg{} -} - -func (m *ModelGithubTrigger) selfListen() tea.Cmd { - return func() tea.Msg { - return <-m.updateSelfChan } } func (m *ModelGithubTrigger) Init() tea.Cmd { - return tea.Batch(textinput.Blink, m.selfListen()) + return tea.Batch(textinput.Blink) } func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -183,8 +170,6 @@ func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { go m.triggerWorkflow() } } - case selfUpdateMsg: - cmds = append(cmds, m.selfListen()) } m.tableTrigger, cmd = m.tableTrigger.Update(msg) @@ -395,7 +380,7 @@ func (m *ModelGithubTrigger) inputController(_ context.Context) { } func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { - defer m.selfUpdate() + defer m.skeleton.TriggerUpdate() m.status.Reset() m.status.SetProgressMessage( @@ -579,9 +564,8 @@ func (m *ModelGithubTrigger) triggerWorkflow() { m.fillEmptyValuesWithDefault() } - m.status.SetProgressMessage( - fmt.Sprintf("[%s@%s]:[%s] Triggering workflow...", - m.selectedRepository.RepositoryName, m.selectedRepository.BranchName, m.selectedWorkflow)) + m.status.SetProgressMessage(fmt.Sprintf("[%s@%s]:[%s] Triggering workflow...", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName, m.selectedWorkflow)) if m.workflowContent == nil { m.status.SetErrorMessage("Workflow contents cannot be empty") @@ -610,10 +594,9 @@ func (m *ModelGithubTrigger) triggerWorkflow() { m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s]:[%s] Workflow triggered.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName, m.selectedWorkflow)) - time.Sleep(1 * time.Second) - m.selfUpdate() + m.skeleton.TriggerUpdate() m.status.SetProgressMessage("Switching to workflow history tab...") - time.Sleep(1500 * time.Millisecond) + time.Sleep(2000 * time.Millisecond) // move these operations under new function named "resetTabSettings" m.workflowContent = nil // reset workflow content @@ -622,8 +605,8 @@ func (m *ModelGithubTrigger) triggerWorkflow() { m.optionValues = nil // reset option values m.selectedRepositoryName = "" // reset selected repository name - UpdateWorkflowHistory(time.Second * 3) // update workflow history - m.skeleton.SetActivePage("history") // switch tab to workflow history + m.skeleton.TriggerUpdateWithMsg(workflowHistoryUpdateMsg{time.Second * 3}) // update workflow history + m.skeleton.SetActivePage("history") // switch tab to workflow history } func (m *ModelGithubTrigger) emptySelector() string { diff --git a/internal/terminal/handler/ghworkflow.go b/internal/terminal/handler/ghworkflow.go index 90007be..d7fbd94 100644 --- a/internal/terminal/handler/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow.go @@ -39,11 +39,9 @@ type ModelGithubWorkflow struct { help help.Model tableTriggerableWorkflow table.Model status *status.ModelStatus - - updateSelfChan chan any } -func SetupModelGithubWorkflow(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflow { +func SetupModelGithubWorkflow(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflow { var tableRowsTriggerableWorkflow []table.Row tableTriggerableWorkflow := table.New( @@ -65,10 +63,10 @@ func SetupModelGithubWorkflow(skeleton *skeleton.Skeleton, githubUseCase gu.UseC Bold(false) tableTriggerableWorkflow.SetStyles(s) - modelStatus := status.SetupModelStatus(skeleton) + modelStatus := status.SetupModelStatus(sk) return &ModelGithubWorkflow{ - skeleton: skeleton, + skeleton: sk, help: help.New(), keys: githubWorkflowKeys, github: githubUseCase, @@ -77,34 +75,17 @@ func SetupModelGithubWorkflow(skeleton *skeleton.Skeleton, githubUseCase gu.UseC selectedRepository: hdltypes.NewSelectedRepository(), syncTriggerableWorkflowsContext: context.Background(), cancelSyncTriggerableWorkflows: func() {}, - updateSelfChan: make(chan any), - } -} - -func (m *ModelGithubWorkflow) selfUpdate() { - m.updateSelfChan <- selfUpdateMsg{} -} - -func (m *ModelGithubWorkflow) selfListen() tea.Cmd { - return func() tea.Msg { - return <-m.updateSelfChan } } func (m *ModelGithubWorkflow) Init() tea.Cmd { - return tea.Batch(m.selfListen()) + return nil } func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd var cmd tea.Cmd - switch msg := msg.(type) { - case selfUpdateMsg: - _ = msg - cmds = append(cmds, m.selfListen()) - } - if m.lastRepository != m.selectedRepository.RepositoryName { m.tableReady = false // reset table ready status m.cancelSyncTriggerableWorkflows() // cancel previous sync @@ -154,7 +135,7 @@ func (m *ModelGithubWorkflow) View() string { } func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { - defer m.selfUpdate() + defer m.skeleton.TriggerUpdate() m.status.Reset() m.status.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching triggerable workflows...", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index 462d504..065ea63 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -49,23 +49,13 @@ type ModelGithubWorkflowHistory struct { status *status.ModelStatus modelTabOptions *taboptions.Options - - updateSelfChan chan any } type workflowHistoryUpdateMsg struct { UpdateAfter time.Duration } -var githubWorkflowHistoryUpdateChan = make(chan workflowHistoryUpdateMsg) - -func UpdateWorkflowHistory(timeAfter time.Duration) { - go func() { - githubWorkflowHistoryUpdateChan <- workflowHistoryUpdateMsg{UpdateAfter: timeAfter} - }() -} - -func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { +func SetupModelGithubWorkflowHistory(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { cfg, err := config.LoadConfig() if err != nil { panic(fmt.Sprintf("failed to load config: %v", err)) @@ -96,13 +86,11 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase BorderStyle(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) - modelStatus := status.SetupModelStatus(skeleton) - tabOptions := taboptions.NewOptions(&modelStatus) - - githubWorkflowHistoryUpdateChan = make(chan workflowHistoryUpdateMsg) + modelStatus := status.SetupModelStatus(sk) + tabOptions := taboptions.NewOptions(sk, &modelStatus) return &ModelGithubWorkflowHistory{ - skeleton: skeleton, + skeleton: sk, liveMode: cfg.Settings.LiveMode.Enabled, liveModeInterval: cfg.Settings.LiveMode.Interval, Help: help.New(), @@ -115,31 +103,13 @@ func SetupModelGithubWorkflowHistory(skeleton *skeleton.Skeleton, githubUseCase syncWorkflowHistoryContext: context.Background(), cancelSyncWorkflowHistory: func() {}, tableStyle: tableStyle, - updateSelfChan: make(chan any), - } -} - -func (m *ModelGithubWorkflowHistory) selfUpdate() { - m.updateSelfChan <- selfUpdateMsg{} -} - -func (m *ModelGithubWorkflowHistory) selfListen() tea.Cmd { - return func() tea.Msg { - return <-m.updateSelfChan } } func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { m.setupOptions() m.ToggleLiveMode() - return tea.Batch( - m.modelTabOptions.Init(), - func() tea.Msg { - return workflowHistoryUpdateMsg{UpdateAfter: time.Second * 1} - }, - m.SelfUpdater(), - m.selfListen(), - ) + return tea.Batch(m.modelTabOptions.Init()) } func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.lastRepository != m.selectedRepository.RepositoryName { @@ -163,7 +133,6 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch { case key.Matches(msg, m.keys.Refresh): go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) - cmds = append(cmds, m.SelfUpdater()) case key.Matches(msg, m.keys.LiveMode): m.liveMode = !m.liveMode if m.liveMode { @@ -178,11 +147,8 @@ func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { go func() { time.Sleep(msg.UpdateAfter) m.syncWorkflowHistory(m.syncWorkflowHistoryContext) + m.skeleton.TriggerUpdate() }() - cmds = append(cmds, m.SelfUpdater()) - case selfUpdateMsg: - // do nothing - cmds = append(cmds, m.selfListen()) } m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) @@ -218,19 +184,13 @@ func (m *ModelGithubWorkflowHistory) View() string { m.tableWorkflowHistory.SetColumns(newTableColumns) } - m.tableWorkflowHistory.SetHeight(termHeight - 18) + m.tableWorkflowHistory.SetHeight(termHeight - 17) return lipgloss.JoinVertical(lipgloss.Top, m.tableStyle.Render(m.tableWorkflowHistory.View()), m.modelTabOptions.View(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } -func (m *ModelGithubWorkflowHistory) SelfUpdater() tea.Cmd { - return func() tea.Msg { - return <-githubWorkflowHistoryUpdateChan - } -} - func (m *ModelGithubWorkflowHistory) setupOptions() { openInBrowser := func() { m.status.SetProgressMessage("Opening in browser...") @@ -311,19 +271,20 @@ func (m *ModelGithubWorkflowHistory) ToggleLiveMode() { select { case <-t.C: if m.liveMode { - githubWorkflowHistoryUpdateChan <- workflowHistoryUpdateMsg{UpdateAfter: time.Nanosecond} + m.skeleton.TriggerUpdateWithMsg(workflowHistoryUpdateMsg{UpdateAfter: time.Nanosecond}) } } } }() } + func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { - defer m.selfUpdate() + defer m.skeleton.TriggerUpdate() m.tableReady = false m.status.Reset() - m.status.SetProgressMessage( - fmt.Sprintf("[%s@%s] Fetching workflow history...", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + m.status.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching workflow history...", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) m.modelTabOptions.SetStatus(taboptions.OptionWait) // delete all rows diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index adeda9f..8a90e89 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -1,29 +1,36 @@ package handler import ( + "fmt" tea "github.com/charmbracelet/bubbletea" + "github.com/termkit/gama/internal/config" gu "github.com/termkit/gama/internal/github/usecase" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" pkgversion "github.com/termkit/gama/pkg/version" "github.com/termkit/skeleton" - "time" ) func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Model { + cfg, err := config.LoadConfig() + if err != nil { + panic(fmt.Sprintf("failed to load config: %v", err)) + } + s := skeleton.NewSkeleton() - s.AddPage("info", "Info", SetupModelInfo(s, githubUseCase, version)). - AddPage("repository", "Repository", SetupModelGithubRepository(s, githubUseCase)). - AddPage("history", "Workflow History", SetupModelGithubWorkflowHistory(s, githubUseCase)). - AddPage("workflow", "Workflow", SetupModelGithubWorkflow(s, githubUseCase)). - AddPage("trigger", "Trigger", SetupModelGithubTrigger(s, githubUseCase)) + s.AddPage("info", "Info", SetupModelInfo(s, githubUseCase, version)) + s.AddPage("repository", "Repository", SetupModelGithubRepository(s, githubUseCase)) + s.AddPage("history", "Workflow History", SetupModelGithubWorkflowHistory(s, githubUseCase)) + s.AddPage("workflow", "Workflow", SetupModelGithubWorkflow(s, githubUseCase)) + s.AddPage("trigger", "Trigger", SetupModelGithubTrigger(s, githubUseCase)) s.SetBorderColor("#ff0055").SetActiveTabBorderColor("#ff0055").SetInactiveTabBorderColor("#82636f").SetWidgetBorderColor("#ff0055") - time.Sleep(100 * time.Millisecond) - s.AddWidget("repositories", "Repository Count: 0") - time.Sleep(100 * time.Millisecond) - s.AddWidget("live", "Live Mode: Off") + if cfg.Settings.LiveMode.Enabled { + s.AddWidget("live", "Live Mode: On") + } else { + s.AddWidget("live", "Live Mode: Off") + } s.SetTerminalViewportWidth(hdltypes.MinTerminalWidth) s.SetTerminalViewportHeight(hdltypes.MinTerminalHeight) diff --git a/internal/terminal/handler/status/status.go b/internal/terminal/handler/status/status.go index 1ab8147..0cc081a 100644 --- a/internal/terminal/handler/status/status.go +++ b/internal/terminal/handler/status/status.go @@ -23,11 +23,6 @@ type ModelStatus struct { messageType MessageType } -type UpdateSelf struct { - Message string - InProgress bool -} - type MessageType string const ( diff --git a/internal/terminal/handler/taboptions/taboptions.go b/internal/terminal/handler/taboptions/taboptions.go index 3733ab5..2a9e940 100644 --- a/internal/terminal/handler/taboptions/taboptions.go +++ b/internal/terminal/handler/taboptions/taboptions.go @@ -2,6 +2,7 @@ package taboptions import ( "fmt" + "github.com/termkit/skeleton" "strings" "time" @@ -11,6 +12,8 @@ import ( ) type Options struct { + skeleton *skeleton.Skeleton + Style lipgloss.Style status *status.ModelStatus @@ -48,7 +51,7 @@ func (o OptionStatus) String() string { return string(o) } -func NewOptions(modelStatus *status.ModelStatus) *Options { +func NewOptions(sk *skeleton.Skeleton, modelStatus *status.ModelStatus) *Options { var b = lipgloss.RoundedBorder() b.Right = "├" b.Left = "┤" @@ -69,6 +72,7 @@ func NewOptions(modelStatus *status.ModelStatus) *Options { optionsWithFunc[0] = func() {} // NO OPERATION return &Options{ + skeleton: sk, Style: OptionsStyle, options: initialOptions, optionsAction: initialOptionsAction, @@ -145,6 +149,7 @@ func (o *Options) resetOptionsWithOriginal() { o.optionsAction[0] = fmt.Sprintf("> %ds", o.timer) time.Sleep(1 * time.Second) o.timer-- + o.skeleton.TriggerUpdate() } o.modelLock = false o.switchToPreviousError() diff --git a/internal/terminal/handler/types.go b/internal/terminal/handler/types.go index c51a3d2..abeebd1 100644 --- a/internal/terminal/handler/types.go +++ b/internal/terminal/handler/types.go @@ -1,5 +1 @@ package handler - -// selfUpdateMsg is a message to trigger update & view -type selfUpdateMsg struct { -} From 8ca134e4cee6f6ff6c8954925196c59fd31a6b5a Mon Sep 17 00:00:00 2001 From: Engin Date: Wed, 18 Dec 2024 01:51:28 +0300 Subject: [PATCH 40/51] remove go.work.sum --- go.work.sum | 155 ---------------------------------------------------- 1 file changed, 155 deletions(-) delete mode 100644 go.work.sum diff --git a/go.work.sum b/go.work.sum deleted file mode 100644 index 1e16e82..0000000 --- a/go.work.sum +++ /dev/null @@ -1,155 +0,0 @@ -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8= -cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk= -cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI= -cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= -cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= -cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w= -cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= -github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= -github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ= -github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= -github.com/charmbracelet/x/input v0.1.3 h1:oy4TMhyGQsYs/WWJwu1ELUMFnjiUAXwtDf048fHbCkg= -github.com/charmbracelet/x/input v0.1.3/go.mod h1:1gaCOyw1KI9e2j00j/BBZ4ErzRZqa05w0Ghn83yIhKU= -github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg= -github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= -github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= -github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 h1:zC34cGQu69FG7qzJ3WiKW244WfhDC3xxYMeNOX2gtUQ= -github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8= -github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= -github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= -github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= -github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= -github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/nats-io/nats.go v1.34.0 h1:fnxnPCNiwIG5w08rlMcEKTUw4AV/nKyGCOJE8TdhSPk= -github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= -github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= -github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= -github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= -github.com/sagikazarmark/crypt v0.19.0 h1:WMyLTjHBo64UvNcWqpzY3pbZTYgnemZU8FBZigKc42E= -github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= -github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= -github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -go.etcd.io/etcd/api/v3 v3.5.12 h1:W4sw5ZoU2Juc9gBWuLk5U6fHfNVyY1WC5g9uiXZio/c= -go.etcd.io/etcd/api/v3 v3.5.12/go.mod h1:Ot+o0SWSyT6uHhA56al1oCED0JImsRiU9Dc26+C2a+4= -go.etcd.io/etcd/client/pkg/v3 v3.5.12 h1:EYDL6pWwyOsylrQyLp2w+HkQ46ATiOvoEdMarindU2A= -go.etcd.io/etcd/client/pkg/v3 v3.5.12/go.mod h1:seTzl2d9APP8R5Y2hFL3NVlD6qC/dOT+3kvrqPyTas4= -go.etcd.io/etcd/client/v2 v2.305.12 h1:0m4ovXYo1CHaA/Mp3X/Fak5sRNIWf01wk/X1/G3sGKI= -go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E= -go.etcd.io/etcd/client/v3 v3.5.12 h1:v5lCPXn1pf1Uu3M4laUE2hp/geOTc5uPcYYsNe1lDxg= -go.etcd.io/etcd/client/v3 v3.5.12/go.mod h1:tSbBCakoWmmddL+BKVAJHa9km+O/E+bumDe9mSbPiqw= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= -golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= -golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= -google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= -google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= -google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y= -google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= -google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c h1:lfpJ/2rWPa/kJgxyyXM8PrNnfCzcmxJ265mADgwmvLI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240314234333-6e1732d8331c/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= -google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= From 0e1143165c5ef9fd3878052f73217c5bee8d3a46 Mon Sep 17 00:00:00 2001 From: Engin Date: Thu, 19 Dec 2024 00:57:48 +0300 Subject: [PATCH 41/51] Upgrade the dependencies --- go.mod | 6 +----- go.sum | 2 ++ 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 1a757cf..00ccc17 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,6 @@ module github.com/termkit/gama go 1.23 -replace github.com/termkit/skeleton => ../skeleton - require ( github.com/Masterminds/semver/v3 v3.3.1 github.com/charmbracelet/bubbles v0.20.0 @@ -11,12 +9,10 @@ require ( github.com/charmbracelet/lipgloss v1.0.0 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 - //github.com/termkit/skeleton v0.1.2 + github.com/termkit/skeleton v0.1.3 gopkg.in/yaml.v3 v3.0.1 ) -require github.com/termkit/skeleton v0.0.0-00010101000000-000000000000 - require ( github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect diff --git a/go.sum b/go.sum index 9b2a1ca..70954ca 100644 --- a/go.sum +++ b/go.sum @@ -80,6 +80,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/termkit/skeleton v0.1.3 h1:XiCqLDQXhtU4LhrgDKYHaPFRumngLPs9ssebL8WXHVI= +github.com/termkit/skeleton v0.1.3/go.mod h1:KjHXehkpVm8i3pli9PTG+Lat2PrUBsw6QQe5kbgYXHs= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= From ac225e25b2e46898a951b13351320088e29813ac Mon Sep 17 00:00:00 2001 From: Engin Date: Mon, 6 Jan 2025 00:28:33 +0300 Subject: [PATCH 42/51] Upgrade termkit/skeleton to v0.2.0 and add GetRepositoryBranches functionality with associated types and UI updates for branch selection. --- go.mod | 2 +- go.sum | 2 + internal/github/usecase/ports.go | 1 + internal/github/usecase/types.go | 17 +++ internal/github/usecase/usecase.go | 36 ++++++ internal/terminal/handler/ghrepository.go | 12 ++ internal/terminal/handler/ghworkflow.go | 116 ++++++++++++++++-- .../terminal/handler/ghworkflowhistory.go | 2 +- 8 files changed, 178 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 00ccc17..dd3874a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/charmbracelet/lipgloss v1.0.0 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 - github.com/termkit/skeleton v0.1.3 + github.com/termkit/skeleton v0.2.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 70954ca..a971264 100644 --- a/go.sum +++ b/go.sum @@ -82,6 +82,8 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/termkit/skeleton v0.1.3 h1:XiCqLDQXhtU4LhrgDKYHaPFRumngLPs9ssebL8WXHVI= github.com/termkit/skeleton v0.1.3/go.mod h1:KjHXehkpVm8i3pli9PTG+Lat2PrUBsw6QQe5kbgYXHs= +github.com/termkit/skeleton v0.2.0 h1:Nbs7i5+vsouK25Gl4+vuMB1/7jokZ77HN2wV1IYgpBQ= +github.com/termkit/skeleton v0.2.0/go.mod h1:KjHXehkpVm8i3pli9PTG+Lat2PrUBsw6QQe5kbgYXHs= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= diff --git a/internal/github/usecase/ports.go b/internal/github/usecase/ports.go index a0f706f..b825753 100644 --- a/internal/github/usecase/ports.go +++ b/internal/github/usecase/ports.go @@ -7,6 +7,7 @@ import ( type UseCase interface { GetAuthUser(ctx context.Context) (*GetAuthUserOutput, error) ListRepositories(ctx context.Context, input ListRepositoriesInput) (*ListRepositoriesOutput, error) + GetRepositoryBranches(ctx context.Context, input GetRepositoryBranchesInput) (*GetRepositoryBranchesOutput, error) GetWorkflowHistory(ctx context.Context, input GetWorkflowHistoryInput) (*GetWorkflowHistoryOutput, error) GetTriggerableWorkflows(ctx context.Context, input GetTriggerableWorkflowsInput) (*GetTriggerableWorkflowsOutput, error) InspectWorkflow(ctx context.Context, input InspectWorkflowInput) (*InspectWorkflowOutput, error) diff --git a/internal/github/usecase/types.go b/internal/github/usecase/types.go index 84cc7f8..b398a8e 100644 --- a/internal/github/usecase/types.go +++ b/internal/github/usecase/types.go @@ -31,6 +31,23 @@ type ListRepositoriesOutput struct { Repositories []GithubRepository } +// ------------------------------------------------------------ + +type GetRepositoryBranchesInput struct { + Repository string +} + +type GetRepositoryBranchesOutput struct { + Branches []GithubBranch +} + +type GithubBranch struct { + Name string + IsDefault bool +} + +// ------------------------------------------------------------ + type GithubRepository struct { Name string Private bool diff --git a/internal/github/usecase/usecase.go b/internal/github/usecase/usecase.go index 2c78943..f278a7a 100644 --- a/internal/github/usecase/usecase.go +++ b/internal/github/usecase/usecase.go @@ -75,6 +75,42 @@ func (u useCase) ListRepositories(ctx context.Context, input ListRepositoriesInp }, errors.Join(resultErrs...) } +func (u useCase) GetRepositoryBranches(ctx context.Context, input GetRepositoryBranchesInput) (*GetRepositoryBranchesOutput, error) { + // Get Repository to get the default branch + repository, err := u.githubRepository.GetRepository(ctx, input.Repository) + if err != nil { + return nil, err + } + + var mainBranch = repository.DefaultBranch + + branches, err := u.githubRepository.ListBranches(ctx, input.Repository) + if err != nil { + return nil, err + } + + if len(branches) == 0 { + return &GetRepositoryBranchesOutput{}, nil + } + + var result = []GithubBranch{ + { + Name: mainBranch, + IsDefault: true, + }, + } + + for _, branch := range branches { + result = append(result, GithubBranch{ + Name: branch.Name, + }) + } + + return &GetRepositoryBranchesOutput{ + Branches: result, + }, nil +} + func (u useCase) workerListRepositories(ctx context.Context, repository gr.GithubRepository, results chan<- GithubRepository, errs chan<- error) { getWorkflows, err := u.githubRepository.GetWorkflows(ctx, repository.FullName) if err != nil { diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index 4b9de66..d4c63e4 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -274,6 +274,18 @@ func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { if len(selectedRow) > 0 && selectedRow[0] != "" { m.selectedRepository.RepositoryName = selectedRow[0] m.selectedRepository.BranchName = selectedRow[1] + + workflowCount := selectedRow[3] + if workflowCount != "" { + count, _ := strconv.Atoi(workflowCount) + if count == 0 { + m.skeleton.LockTab("workflow") + m.skeleton.LockTab("trigger") + } else { + m.skeleton.UnlockTab("workflow") + m.skeleton.UnlockTab("trigger") + } + } } } diff --git a/internal/terminal/handler/ghworkflow.go b/internal/terminal/handler/ghworkflow.go index d7fbd94..299b3b9 100644 --- a/internal/terminal/handler/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow.go @@ -4,14 +4,14 @@ import ( "context" "errors" "fmt" - "github.com/termkit/skeleton" - "sort" - "strings" - "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/table" + "github.com/charmbracelet/bubbles/textinput" "github.com/termkit/gama/internal/terminal/handler/status" hdltypes "github.com/termkit/gama/internal/terminal/handler/types" + "github.com/termkit/skeleton" + "sort" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -25,6 +25,7 @@ type ModelGithubWorkflow struct { cancelSyncTriggerableWorkflows context.CancelFunc tableReady bool lastRepository string + mainBranch string // shared properties selectedRepository *hdltypes.SelectedRepository @@ -39,6 +40,7 @@ type ModelGithubWorkflow struct { help help.Model tableTriggerableWorkflow table.Model status *status.ModelStatus + textInput textinput.Model } func SetupModelGithubWorkflow(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflow { @@ -63,6 +65,21 @@ func SetupModelGithubWorkflow(sk *skeleton.Skeleton, githubUseCase gu.UseCase) * Bold(false) tableTriggerableWorkflow.SetStyles(s) + tableTriggerableWorkflow.KeyMap = table.KeyMap{ + LineUp: key.NewBinding( + key.WithKeys("up"), + ), + LineDown: key.NewBinding( + key.WithKeys("down"), + ), + } + + ti := textinput.New() + ti.Focus() + ti.CharLimit = 128 + ti.Placeholder = "Type to switch branch" + ti.ShowSuggestions = true + modelStatus := status.SetupModelStatus(sk) return &ModelGithubWorkflow{ @@ -75,6 +92,7 @@ func SetupModelGithubWorkflow(sk *skeleton.Skeleton, githubUseCase gu.UseCase) * selectedRepository: hdltypes.NewSelectedRepository(), syncTriggerableWorkflowsContext: context.Background(), cancelSyncTriggerableWorkflows: func() {}, + textInput: ti, } } @@ -92,10 +110,38 @@ func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.syncTriggerableWorkflowsContext, m.cancelSyncTriggerableWorkflows = context.WithCancel(context.Background()) m.lastRepository = m.selectedRepository.RepositoryName + m.mainBranch = m.selectedRepository.BranchName go m.syncTriggerableWorkflows(m.syncTriggerableWorkflowsContext) + go m.syncBranches(m.syncTriggerableWorkflowsContext) + } + + var selectedBranch = m.textInput.Value() + if selectedBranch != "" { + var isBranchExist bool + for _, branch := range m.textInput.AvailableSuggestions() { + if branch == selectedBranch { + isBranchExist = true + m.selectedRepository.BranchName = selectedBranch + break + } + } + + if !isBranchExist { + m.status.SetErrorMessage(fmt.Sprintf("Branch %s is not exist", selectedBranch)) + m.skeleton.LockTabsToTheRight() + } else { + m.skeleton.UnlockTabs() + } + } + if selectedBranch == "" { + m.selectedRepository.BranchName = m.mainBranch + m.skeleton.UnlockTabs() } + m.textInput, cmd = m.textInput.Update(msg) + cmds = append(cmds, cmd) + m.tableTriggerableWorkflow, cmd = m.tableTriggerableWorkflow.Update(msg) cmds = append(cmds, cmd) @@ -127,11 +173,28 @@ func (m *ModelGithubWorkflow) View() string { m.tableTriggerableWorkflow.SetHeight(termHeight - 17) } - doc := strings.Builder{} - doc.WriteString(style.Render(m.tableTriggerableWorkflow.View())) - doc.WriteString("\n\n\n") + return lipgloss.JoinVertical(lipgloss.Top, + style.Render(m.tableTriggerableWorkflow.View()), + m.viewSearchBar(), + m.status.View(), + helpWindowStyle.Render(m.ViewHelp()), + ) +} + +func (m *ModelGithubWorkflow) viewSearchBar() string { + // Define window style + windowStyle := lipgloss.NewStyle(). + Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#3b698f")). + Padding(0, 1). + Width(m.skeleton.GetTerminalWidth() - 6).MarginLeft(1) + + if len(m.textInput.AvailableSuggestions()) > 0 && m.textInput.Value() == "" { + var mainBranch = m.mainBranch + m.textInput.Placeholder = "Type to switch branch (default: " + mainBranch + ")" + } - return lipgloss.JoinVertical(lipgloss.Top, doc.String(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) + return windowStyle.Render(m.textInput.View()) } func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { @@ -175,10 +238,47 @@ func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { m.tableTriggerableWorkflow.SetRows(tableRowsTriggerableWorkflow) + if len(tableRowsTriggerableWorkflow) > 0 { + m.tableTriggerableWorkflow.SetCursor(0) + } + m.tableReady = true m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s] Triggerable workflows fetched.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) } +func (m *ModelGithubWorkflow) syncBranches(ctx context.Context) { + defer m.skeleton.TriggerUpdate() + + m.status.Reset() + m.status.SetProgressMessage(fmt.Sprintf("[%s] Fetching branches...", m.selectedRepository.RepositoryName)) + + branches, err := m.github.GetRepositoryBranches(ctx, gu.GetRepositoryBranchesInput{ + Repository: m.selectedRepository.RepositoryName, + }) + if errors.Is(err, context.Canceled) { + return + } else if err != nil { + m.status.SetError(err) + m.status.SetErrorMessage("Branches cannot be listed") + return + } + + if branches == nil || len(branches.Branches) == 0 { + m.selectedRepository.BranchName = "" + m.status.SetDefaultMessage(fmt.Sprintf("[%s] No branches found.", m.selectedRepository.RepositoryName)) + return + } + + var bs = make([]string, len(branches.Branches)) + for i, branch := range branches.Branches { + bs[i] = branch.Name + } + + m.textInput.SetSuggestions(bs) + + m.status.SetSuccessMessage(fmt.Sprintf("[%s] Branches fetched.", m.selectedRepository.RepositoryName)) +} + func (m *ModelGithubWorkflow) handleTableInputs(_ context.Context) { if !m.tableReady { return diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index 065ea63..38a930a 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -307,7 +307,7 @@ func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { if len(workflowHistory.Workflows) == 0 { m.modelTabOptions.SetStatus(taboptions.OptionNone) - m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] No workflows found.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] No workflow history found.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) return } From 2a0de1089f18d2b6549a037c2c8c9c6f0766d078 Mon Sep 17 00:00:00 2001 From: Engin Date: Mon, 6 Jan 2025 02:34:32 +0300 Subject: [PATCH 43/51] Refactor terminal handler models and improve UI components - Introduced a new ModelStatus for managing application status messages. - Consolidated UI components and improved layout calculations for better rendering. - Enhanced error handling and messaging across various models. - Updated the SetupModel functions to streamline initialization of models. - Removed obsolete types and improved code organization for maintainability. - Added functionality for handling repository and workflow selections more effectively. - Improved user experience with better feedback messages and UI updates. This commit enhances the overall structure and usability of the terminal handler components. --- internal/terminal/handler/ghinformation.go | 193 +++- internal/terminal/handler/ghrepository.go | 561 +++++---- internal/terminal/handler/ghtrigger.go | 1022 ++++++++++------- internal/terminal/handler/ghworkflow.go | 459 +++++--- .../terminal/handler/ghworkflowhistory.go | 562 +++++---- internal/terminal/handler/handler.go | 5 +- internal/terminal/handler/keymap.go | 1 + .../terminal/handler/{status => }/status.go | 20 +- .../handler/{taboptions => }/taboptions.go | 64 +- internal/terminal/handler/types.go | 55 + internal/terminal/handler/types/style.go | 19 - internal/terminal/handler/types/types.go | 30 - 12 files changed, 1873 insertions(+), 1118 deletions(-) rename internal/terminal/handler/{status => }/status.go (86%) rename internal/terminal/handler/{taboptions => }/taboptions.go (72%) delete mode 100644 internal/terminal/handler/types/style.go delete mode 100644 internal/terminal/handler/types/types.go diff --git a/internal/terminal/handler/ghinformation.go b/internal/terminal/handler/ghinformation.go index e2f03ae..1b5fcdc 100644 --- a/internal/terminal/handler/ghinformation.go +++ b/internal/terminal/handler/ghinformation.go @@ -3,73 +3,83 @@ package handler import ( "context" "fmt" + "strings" + "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" gu "github.com/termkit/gama/internal/github/usecase" - "github.com/termkit/gama/internal/terminal/handler/status" - ts "github.com/termkit/gama/internal/terminal/handler/types" pkgversion "github.com/termkit/gama/pkg/version" "github.com/termkit/skeleton" - "strings" ) +// ----------------------------------------------------------------------------- +// Model Definition +// ----------------------------------------------------------------------------- + type ModelInfo struct { + // Core dependencies skeleton *skeleton.Skeleton + github gu.UseCase + version pkgversion.Version + // UI Components + help help.Model + status *ModelStatus + keys githubInformationKeyMap + + // Application state logo string releaseURL string - newVersionAvailableMsg string applicationDescription string - - version pkgversion.Version - - // use cases - github gu.UseCase - - // models - help help.Model - status *status.ModelStatus - - // keymap - keys githubInformationKeyMap + newVersionAvailableMsg string } +// ----------------------------------------------------------------------------- +// Constructor & Initialization +// ----------------------------------------------------------------------------- + func SetupModelInfo(sk *skeleton.Skeleton, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { - modelStatus := status.SetupModelStatus(sk) + const releaseURL = "https://github.com/termkit/gama/releases" - const ( - releaseURL = "https://github.com/termkit/gama/releases" + return &ModelInfo{ + // Initialize core dependencies + skeleton: sk, + github: githubUseCase, + version: version, + + // Initialize UI components + help: help.New(), + status: SetupModelStatus(sk), + keys: githubInformationKeys, + + // Initialize application state + logo: defaultLogo, + releaseURL: releaseURL, + } +} - applicationName = ` +const defaultLogo = ` ..|'''.| | '|| ||' | .|' ' ||| ||| ||| ||| || .... | || |'|..'|| | || '|. || .''''|. | '|' || .''''|. ''|...'| .|. .||. .|. | .||. .|. .||. ` - ) - return &ModelInfo{ - skeleton: sk, - releaseURL: releaseURL, - logo: applicationName, - github: githubUseCase, - version: version, - help: help.New(), - keys: githubInformationKeys, - status: &modelStatus, - } -} +// ----------------------------------------------------------------------------- +// Bubbletea Model Implementation +// ----------------------------------------------------------------------------- func (m *ModelInfo) Init() tea.Cmd { - m.applicationDescription = fmt.Sprintf("Github Actions Manager (%s)", m.version.CurrentVersion()) + m.initializeAppDescription() + m.startBackgroundTasks() - go m.checkUpdates(context.Background()) - go m.testConnection(context.Background()) - - return tea.Batch(tea.EnterAltScreen, tea.SetWindowTitle("GitHub Actions Manager (GAMA)")) + return tea.Batch( + tea.EnterAltScreen, + tea.SetWindowTitle("GitHub Actions Manager (GAMA)"), + ) } func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -80,45 +90,93 @@ func (m *ModelInfo) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit } } - return m, nil } func (m *ModelInfo) View() string { - infoDoc := strings.Builder{} + return lipgloss.JoinVertical(lipgloss.Center, + m.renderMainContent(), + m.status.View(), + m.renderHelpWindow(), + ) +} - helpWindowStyle := ts.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) +// ----------------------------------------------------------------------------- +// UI Rendering +// ----------------------------------------------------------------------------- - requiredNewLinesForCenter := m.skeleton.GetTerminalHeight()/2 - 11 - if requiredNewLinesForCenter < 0 { - requiredNewLinesForCenter = 0 - } +func (m *ModelInfo) renderMainContent() string { + content := strings.Builder{} + + // Add vertical centering + centerPadding := m.calculateCenterPadding() + content.WriteString(strings.Repeat("\n", centerPadding)) + + // Add main content + content.WriteString(lipgloss.JoinVertical(lipgloss.Center, + m.logo, + m.applicationDescription, + m.newVersionAvailableMsg, + )) + + // Add bottom padding + bottomPadding := m.calculateBottomPadding(content.String()) + content.WriteString(strings.Repeat("\n", bottomPadding)) - infoDoc.WriteString(strings.Repeat("\n", requiredNewLinesForCenter)) - infoDoc.WriteString(lipgloss.JoinVertical(lipgloss.Center, m.logo, m.applicationDescription, m.newVersionAvailableMsg)) + return content.String() +} + +func (m *ModelInfo) renderHelpWindow() string { + helpStyle := WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + return helpStyle.Render(m.ViewHelp()) +} + +// ----------------------------------------------------------------------------- +// Layout Calculations +// ----------------------------------------------------------------------------- - requiredNewlinesForPadding := m.skeleton.GetTerminalHeight() - lipgloss.Height(infoDoc.String()) - 12 +func (m *ModelInfo) calculateCenterPadding() int { + padding := m.skeleton.GetTerminalHeight()/2 - 11 + return max(0, padding) +} + +func (m *ModelInfo) calculateBottomPadding(content string) int { + padding := m.skeleton.GetTerminalHeight() - lipgloss.Height(content) - 12 + return max(0, padding) +} - infoDoc.WriteString(strings.Repeat("\n", max(0, requiredNewlinesForPadding))) +// ----------------------------------------------------------------------------- +// Application State Management +// ----------------------------------------------------------------------------- - str := lipgloss.JoinVertical(lipgloss.Center, infoDoc.String(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) +func (m *ModelInfo) initializeAppDescription() { + m.applicationDescription = fmt.Sprintf("Github Actions Manager (%s)", m.version.CurrentVersion()) +} - return str +func (m *ModelInfo) startBackgroundTasks() { + go m.checkUpdates(context.Background()) + go m.testConnection(context.Background()) } +// ----------------------------------------------------------------------------- +// Background Tasks +// ----------------------------------------------------------------------------- + func (m *ModelInfo) checkUpdates(ctx context.Context) { defer m.skeleton.TriggerUpdate() isUpdateAvailable, version, err := m.version.IsUpdateAvailable(ctx) if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("failed to check updates") - m.newVersionAvailableMsg = fmt.Sprintf("failed to check updates.\nPlease visit: %s", m.releaseURL) + m.handleUpdateError(err) return } if isUpdateAvailable { - m.newVersionAvailableMsg = fmt.Sprintf("New version available: %s\nPlease visit: %s", version, m.releaseURL) + m.newVersionAvailableMsg = fmt.Sprintf( + "New version available: %s\nPlease visit: %s", + version, + m.releaseURL, + ) } } @@ -130,12 +188,33 @@ func (m *ModelInfo) testConnection(ctx context.Context) { _, err := m.github.GetAuthUser(ctx) if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("failed to test connection, please check your token&permission") - m.skeleton.LockTabs() + m.handleConnectionError(err) return } + m.handleSuccessfulConnection() +} + +// ----------------------------------------------------------------------------- +// Error Handling +// ----------------------------------------------------------------------------- + +func (m *ModelInfo) handleUpdateError(err error) { + m.status.SetError(err) + m.status.SetErrorMessage("failed to check updates") + m.newVersionAvailableMsg = fmt.Sprintf( + "failed to check updates.\nPlease visit: %s", + m.releaseURL, + ) +} + +func (m *ModelInfo) handleConnectionError(err error) { + m.status.SetError(err) + m.status.SetErrorMessage("failed to test connection, please check your token&permission") + m.skeleton.LockTabs() +} + +func (m *ModelInfo) handleSuccessfulConnection() { m.status.Reset() m.status.SetSuccessMessage("Welcome to GAMA!") m.skeleton.UnlockTabs() diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index d4c63e4..6f3d3a3 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -4,6 +4,10 @@ import ( "context" "errors" "fmt" + "strconv" + "strings" + "time" + "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/table" @@ -12,52 +16,103 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/termkit/gama/internal/github/domain" gu "github.com/termkit/gama/internal/github/usecase" - "github.com/termkit/gama/internal/terminal/handler/status" - "github.com/termkit/gama/internal/terminal/handler/taboptions" - hdltypes "github.com/termkit/gama/internal/terminal/handler/types" "github.com/termkit/gama/pkg/browser" "github.com/termkit/skeleton" - "strconv" - "strings" ) +// ----------------------------------------------------------------------------- +// Model Definition +// ----------------------------------------------------------------------------- + type ModelGithubRepository struct { + // Core dependencies skeleton *skeleton.Skeleton - // current handler's properties - syncRepositoriesContext context.Context - cancelSyncRepositories context.CancelFunc - tableReady bool + github gu.UseCase - // shared properties - selectedRepository *hdltypes.SelectedRepository + // UI State + tableReady bool - // use cases - github gu.UseCase + // Context management + syncRepositoriesContext context.Context + cancelSyncRepositories context.CancelFunc - // keymap - Keys githubRepositoryKeyMap + // Shared state + selectedRepository *SelectedRepository - // models + // UI Components help help.Model + Keys githubRepositoryKeyMap tableGithubRepository table.Model searchTableGithubRepository table.Model - status *status.ModelStatus - - modelTabOptions *taboptions.Options - - textInput textinput.Model + status *ModelStatus + textInput textinput.Model + modelTabOptions *ModelTabOptions } +// ----------------------------------------------------------------------------- +// Constructor & Initialization +// ----------------------------------------------------------------------------- + func SetupModelGithubRepository(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubRepository { - var tableRowsGithubRepository []table.Row + m := &ModelGithubRepository{ + // Initialize core dependencies + skeleton: sk, + github: githubUseCase, + + // Initialize UI components + help: help.New(), + Keys: githubRepositoryKeys, + status: SetupModelStatus(sk), + textInput: setupTextInput(), + modelTabOptions: NewOptions(sk, SetupModelStatus(sk)), + + // Initialize state + selectedRepository: NewSelectedRepository(), + syncRepositoriesContext: context.Background(), + cancelSyncRepositories: func() {}, + } + + // Setup tables + m.tableGithubRepository = setupMainTable() + m.searchTableGithubRepository = setupSearchTable() - tableGithubRepository := table.New( + return m +} + +func setupMainTable() table.Model { + t := table.New( table.WithColumns(tableColumnsGithubRepository), - table.WithRows(tableRowsGithubRepository), + table.WithRows([]table.Row{}), table.WithFocused(true), table.WithHeight(13), ) + // Apply styles + t.SetStyles(defaultTableStyles()) + + // Apply keymap + t.KeyMap = defaultTableKeyMap() + + return t +} + +func setupSearchTable() table.Model { + return table.New( + table.WithColumns(tableColumnsGithubRepository), + table.WithRows([]table.Row{}), + ) +} + +func setupTextInput() textinput.Model { + ti := textinput.New() + ti.Blur() + ti.CharLimit = 128 + ti.Placeholder = "Type to search repository" + ti.ShowSuggestions = false + return ti +} + +func defaultTableStyles() table.Styles { s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). @@ -68,130 +123,194 @@ func SetupModelGithubRepository(sk *skeleton.Skeleton, githubUseCase gu.UseCase) Foreground(lipgloss.Color("229")). Background(lipgloss.Color("57")). Bold(false) - tableGithubRepository.SetStyles(s) - - tableGithubRepository.KeyMap = table.KeyMap{ - LineUp: key.NewBinding( - key.WithKeys("up"), - key.WithHelp("↑", "up"), - ), - LineDown: key.NewBinding( - key.WithKeys("down"), - key.WithHelp("↓", "down"), - ), - PageUp: key.NewBinding( - key.WithKeys("pgup"), - key.WithHelp("pgup", "page up"), - ), - PageDown: key.NewBinding( - key.WithKeys("pgdown", " "), - key.WithHelp("pgdn", "page down"), - ), - GotoTop: key.NewBinding( - key.WithKeys("home"), - key.WithHelp("home", "go to start"), - ), - GotoBottom: key.NewBinding( - key.WithKeys("end"), - key.WithHelp("end", "go to end"), - ), - } - - ti := textinput.New() - ti.Blur() - ti.CharLimit = 128 - ti.Placeholder = "Type to search repository" - ti.ShowSuggestions = false // disable suggestions, it will be enabled future. - - // setup models - modelStatus := status.SetupModelStatus(sk) - tabOptions := taboptions.NewOptions(sk, &modelStatus) - - return &ModelGithubRepository{ - skeleton: sk, - help: help.New(), - Keys: githubRepositoryKeys, - github: githubUseCase, - tableGithubRepository: tableGithubRepository, - status: &modelStatus, - selectedRepository: hdltypes.NewSelectedRepository(), - modelTabOptions: tabOptions, - textInput: ti, - syncRepositoriesContext: context.Background(), - cancelSyncRepositories: func() {}, - } + return s } -func (m *ModelGithubRepository) Init() tea.Cmd { - openInBrowser := func() { - m.status.SetProgressMessage("Opening in browser...") - - err := browser.OpenInBrowser(fmt.Sprintf("https://github.com/%s", m.selectedRepository.RepositoryName)) - if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage(fmt.Sprintf("Cannot open in browser: %v", err)) - return - } - - m.status.SetSuccessMessage("Opened in browser") +func defaultTableKeyMap() table.KeyMap { + // We use "up" and "down" for both line up and down, we do not use "k" and "j" to prevent conflict with text input + return table.KeyMap{ + LineUp: key.NewBinding(key.WithKeys("up"), key.WithHelp("↑", "up")), + LineDown: key.NewBinding(key.WithKeys("down"), key.WithHelp("↓", "down")), + PageUp: key.NewBinding(key.WithKeys("pgup"), key.WithHelp("pgup", "page up")), + PageDown: key.NewBinding(key.WithKeys("pgdown", " "), key.WithHelp("pgdn", "page down")), + GotoTop: key.NewBinding(key.WithKeys("home"), key.WithHelp("home", "go to start")), + GotoBottom: key.NewBinding(key.WithKeys("end"), key.WithHelp("end", "go to end")), } +} - m.modelTabOptions.AddOption("Open in browser", openInBrowser) +// ----------------------------------------------------------------------------- +// Bubbletea Model Implementation +// ----------------------------------------------------------------------------- +func (m *ModelGithubRepository) Init() tea.Cmd { + m.setupBrowserOption() go m.syncRepositories(m.syncRepositoriesContext) - return tea.Batch(m.modelTabOptions.Init()) + return tea.Tick(time.Millisecond*100, func(t time.Time) tea.Msg { + return initSyncMsg{} + }) } func (m *ModelGithubRepository) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd var cmd tea.Cmd - var textInputMsg = msg + inputMsg := msg switch msg := msg.(type) { + case initSyncMsg: + m.modelTabOptions.SetStatus(StatusIdle) + m.tableGithubRepository.SetCursor(0) + return m, nil case tea.KeyMsg: - switch { - case key.Matches(msg, m.Keys.Refresh): - m.tableReady = false // reset table ready status - m.cancelSyncRepositories() // cancel previous sync + // Handle number keys for tab options + if m.isNumber(msg.String()) { + inputMsg = tea.KeyMsg{} + } + + // Handle refresh key + if key.Matches(msg, m.Keys.Refresh) { + m.tableReady = false + m.cancelSyncRepositories() m.syncRepositoriesContext, m.cancelSyncRepositories = context.WithCancel(context.Background()) go m.syncRepositories(m.syncRepositoriesContext) - case msg.String() == " " || m.isNumber(msg.String()): - textInputMsg = tea.KeyMsg{} - case m.isCharAndSymbol(msg.Runes): - m.tableGithubRepository.GotoTop() - m.tableGithubRepository.SetCursor(0) - m.searchTableGithubRepository.GotoTop() - m.searchTableGithubRepository.SetCursor(0) + return m, nil } + + // Handle character input for search + if m.isCharAndSymbol(msg.Runes) { + m.resetTableCursors() + } + } + + // Update text input and search functionality + if cmd := m.updateTextInput(inputMsg); cmd != nil { + cmds = append(cmds, cmd) + } + + // Update main table and handle row selection + if cmd := m.updateTable(msg); cmd != nil { + cmds = append(cmds, cmd) } - m.textInput, cmd = m.textInput.Update(textInputMsg) + m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) cmds = append(cmds, cmd) + return m, tea.Batch(cmds...) +} + +func (m *ModelGithubRepository) updateTextInput(msg tea.Msg) tea.Cmd { + var cmd tea.Cmd + m.textInput, cmd = m.textInput.Update(msg) m.updateTableRowsBySearchBar() + return cmd +} +func (m *ModelGithubRepository) updateTable(msg tea.Msg) tea.Cmd { + var cmds []tea.Cmd + var cmd tea.Cmd + + // Update main table m.tableGithubRepository, cmd = m.tableGithubRepository.Update(msg) cmds = append(cmds, cmd) + // Update search table m.searchTableGithubRepository, cmd = m.searchTableGithubRepository.Update(msg) cmds = append(cmds, cmd) + // Handle table selection m.handleTableInputs(m.syncRepositoriesContext) m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) cmds = append(cmds, cmd) - return m, tea.Batch(cmds...) + return tea.Batch(cmds...) } func (m *ModelGithubRepository) View() string { - var baseStyle = lipgloss.NewStyle(). + return lipgloss.JoinVertical(lipgloss.Top, + m.renderTable(), + m.renderSearchBar(), + m.modelTabOptions.View(), + m.status.View(), + m.renderHelp(), + ) +} + + +// ----------------------------------------------------------------------------- +// UI Rendering +// ----------------------------------------------------------------------------- + +func (m *ModelGithubRepository) renderTable() string { + baseStyle := lipgloss.NewStyle(). BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) + BorderForeground(lipgloss.Color("#3b698f")). + MarginLeft(1) + + // Update table dimensions + m.updateTableDimensions() - helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + return baseStyle.Render(m.tableGithubRepository.View()) +} +func (m *ModelGithubRepository) renderSearchBar() string { + style := lipgloss.NewStyle(). + Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#3b698f")). + Padding(0, 1). + Width(m.skeleton.GetTerminalWidth() - 6). + MarginLeft(1) + + if len(m.textInput.Value()) > 0 { + style = style.BorderForeground(lipgloss.Color("39")) + } + + return style.Render(m.textInput.View()) +} + +func (m *ModelGithubRepository) renderHelp() string { + helpStyle := WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + return helpStyle.Render(m.ViewHelp()) +} + +// ----------------------------------------------------------------------------- +// Data Synchronization +// ----------------------------------------------------------------------------- + +func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { + defer m.skeleton.TriggerUpdate() + + m.status.Reset() + m.status.SetProgressMessage("Fetching repositories...") + m.clearTables() + + repos, err := m.fetchRepositories(ctx) + if err != nil { + m.handleFetchError(err) + return + } + + m.updateRepositoryData(repos) +} + +func (m *ModelGithubRepository) fetchRepositories(ctx context.Context) (*gu.ListRepositoriesOutput, error) { + return m.github.ListRepositories(ctx, gu.ListRepositoriesInput{ + Limit: 100, + Page: 5, + Sort: domain.SortByUpdated, + }) +} + +// ----------------------------------------------------------------------------- +// Table Management +// ----------------------------------------------------------------------------- + +func (m *ModelGithubRepository) clearTables() { + m.tableGithubRepository.SetRows([]table.Row{}) + m.searchTableGithubRepository.SetRows([]table.Row{}) +} + +func (m *ModelGithubRepository) updateTableDimensions() { var tableWidth int for _, t := range tableColumnsGithubRepository { tableWidth += t.Width @@ -204,139 +323,181 @@ func (m *ModelGithubRepository) View() string { m.tableGithubRepository.SetColumns(newTableColumns) m.tableGithubRepository.SetHeight(m.skeleton.GetTerminalHeight() - 20) } - - return lipgloss.JoinVertical(lipgloss.Top, - baseStyle.Render(m.tableGithubRepository.View()), m.viewSearchBar(), - m.modelTabOptions.View(), m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } -func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { - defer m.skeleton.TriggerUpdate() - defer m.modelTabOptions.SetStatus(taboptions.OptionIdle) - - m.status.Reset() // reset previous errors - m.modelTabOptions.SetStatus(taboptions.OptionWait) - m.status.SetProgressMessage("Fetching repositories...") +func (m *ModelGithubRepository) resetTableCursors() { + m.tableGithubRepository.GotoTop() + m.tableGithubRepository.SetCursor(0) + m.searchTableGithubRepository.GotoTop() + m.searchTableGithubRepository.SetCursor(0) +} - // delete all rows - m.tableGithubRepository.SetRows([]table.Row{}) - m.searchTableGithubRepository.SetRows([]table.Row{}) +// ----------------------------------------------------------------------------- +// Repository Data Management +// ----------------------------------------------------------------------------- - 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.status.SetError(err) - m.status.SetErrorMessage("Repositories cannot be listed") +func (m *ModelGithubRepository) updateRepositoryData(repos *gu.ListRepositoriesOutput) { + if len(repos.Repositories) == 0 { + m.handleEmptyRepositories() return } - if len(repositories.Repositories) == 0 { - m.modelTabOptions.SetStatus(taboptions.OptionNone) - m.status.SetDefaultMessage("No repositories found") - m.textInput.Blur() - return - } + m.updateWidgetCount(len(repos.Repositories)) + m.updateTableRows(repos.Repositories) + m.finalizeTableUpdate() +} - m.skeleton.UpdateWidgetValue("repositories", fmt.Sprintf("Repository Count: %d", len(repositories.Repositories))) +func (m *ModelGithubRepository) handleEmptyRepositories() { + m.modelTabOptions.SetStatus(StatusNone) + m.status.SetDefaultMessage("No repositories found") + m.textInput.Blur() +} - 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))}) +func (m *ModelGithubRepository) updateWidgetCount(count int) { + m.skeleton.UpdateWidgetValue("repositories", fmt.Sprintf("Repository Count: %d", count)) +} + +func (m *ModelGithubRepository) updateTableRows(repositories []gu.GithubRepository) { + rows := make([]table.Row, 0, len(repositories)) + for _, repo := range repositories { + rows = append(rows, table.Row{ + repo.Name, + repo.DefaultBranch, + strconv.Itoa(repo.Stars), + strconv.Itoa(len(repo.Workflows)), + }) } - m.tableGithubRepository.SetRows(tableRowsGithubRepository) - m.searchTableGithubRepository.SetRows(tableRowsGithubRepository) + m.tableGithubRepository.SetRows(rows) + m.searchTableGithubRepository.SetRows(rows) +} - // set cursor to 0 +func (m *ModelGithubRepository) finalizeTableUpdate() { m.tableGithubRepository.SetCursor(0) m.searchTableGithubRepository.SetCursor(0) - m.tableReady = true m.textInput.Focus() m.status.SetSuccessMessage("Repositories fetched") -} -func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { - if !m.tableReady { - return - } + m.skeleton.TriggerUpdateWithMsg(initSyncMsg{}) +} - // To avoid go routine leak - selectedRow := m.tableGithubRepository.SelectedRow() +// ----------------------------------------------------------------------------- +// Search Functionality +// ----------------------------------------------------------------------------- - // Synchronize selected repository name with parent model - if len(selectedRow) > 0 && selectedRow[0] != "" { - m.selectedRepository.RepositoryName = selectedRow[0] - m.selectedRepository.BranchName = selectedRow[1] - - workflowCount := selectedRow[3] - if workflowCount != "" { - count, _ := strconv.Atoi(workflowCount) - if count == 0 { - m.skeleton.LockTab("workflow") - m.skeleton.LockTab("trigger") - } else { - m.skeleton.UnlockTab("workflow") - m.skeleton.UnlockTab("trigger") - } +func (m *ModelGithubRepository) updateTableRowsBySearchBar() { + searchValue := m.textInput.Value() + rows := m.searchTableGithubRepository.Rows() + + // Filter rows based on repository name + filteredRows := make([]table.Row, 0, len(rows)) + for _, row := range rows { + if strings.Contains(row[0], searchValue) { + filteredRows = append(filteredRows, row) } } + + if len(filteredRows) == 0 { + m.clearSelectedRepository() + } + + m.tableGithubRepository.SetRows(filteredRows) } -func (m *ModelGithubRepository) viewSearchBar() string { - // Define window style - windowStyle := lipgloss.NewStyle(). - Border(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("#3b698f")). - Padding(0, 1). - Width(m.skeleton.GetTerminalWidth() - 6).MarginLeft(1) +func (m *ModelGithubRepository) clearSelectedRepository() { + m.selectedRepository.RepositoryName = "" + m.selectedRepository.BranchName = "" + m.selectedRepository.WorkflowName = "" +} - if len(m.textInput.Value()) > 0 { - windowStyle = windowStyle.BorderForeground(lipgloss.Color("39")) - } +// ----------------------------------------------------------------------------- +// Input Validation & Handling +// ----------------------------------------------------------------------------- - return windowStyle.Render(m.textInput.View()) +func (m *ModelGithubRepository) isNumber(s string) bool { + _, err := strconv.Atoi(s) + return err == nil } -func (m *ModelGithubRepository) updateTableRowsBySearchBar() { - var tableRowsGithubRepository = make([]table.Row, 0, len(m.tableGithubRepository.Rows())) +func (m *ModelGithubRepository) isCharAndSymbol(r []rune) bool { + const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_./" + for _, c := range r { + if strings.ContainsRune(chars, c) { + return true + } + } + return false +} + +// ----------------------------------------------------------------------------- +// Browser Integration +// ----------------------------------------------------------------------------- + +func (m *ModelGithubRepository) setupBrowserOption() { + openInBrowser := func() { + m.status.SetProgressMessage("Opening in browser...") - for _, r := range m.searchTableGithubRepository.Rows() { - if strings.Contains(r[0], m.textInput.Value()) { - tableRowsGithubRepository = append(tableRowsGithubRepository, r) + url := fmt.Sprintf("https://github.com/%s", m.selectedRepository.RepositoryName) + if err := browser.OpenInBrowser(url); err != nil { + m.status.SetError(err) + m.status.SetErrorMessage(fmt.Sprintf("Cannot open in browser: %v", err)) + return } + + m.status.SetSuccessMessage("Opened in browser") } - if len(tableRowsGithubRepository) == 0 { - m.selectedRepository.RepositoryName = "" - m.selectedRepository.BranchName = "" - m.selectedRepository.WorkflowName = "" + m.modelTabOptions.AddOption("Open in browser", openInBrowser) +} + +// ----------------------------------------------------------------------------- +// Error Handling +// ----------------------------------------------------------------------------- + +func (m *ModelGithubRepository) handleFetchError(err error) { + if errors.Is(err, context.Canceled) { + return } - m.tableGithubRepository.SetRows(tableRowsGithubRepository) + m.status.SetError(err) + m.status.SetErrorMessage("Repositories cannot be listed") } -func (m *ModelGithubRepository) isNumber(s string) bool { - if _, err := strconv.Atoi(s); err == nil { - return true +// ----------------------------------------------------------------------------- +// Table Selection Handling +// ----------------------------------------------------------------------------- + +func (m *ModelGithubRepository) handleTableInputs(_ context.Context) { + if !m.tableReady { + return } - return false + selectedRow := m.tableGithubRepository.SelectedRow() + if len(selectedRow) > 0 && selectedRow[0] != "" { + m.updateSelectedRepository(selectedRow) + } } -func (m *ModelGithubRepository) isCharAndSymbol(r []rune) bool { - const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_./" - for _, c := range r { - if strings.ContainsRune(chars, c) { - return true - } +func (m *ModelGithubRepository) updateSelectedRepository(row []string) { + m.selectedRepository.RepositoryName = row[0] + m.selectedRepository.BranchName = row[1] + + if workflowCount := row[3]; workflowCount != "" { + m.handleWorkflowTabLocking(workflowCount) } +} - return false +func (m *ModelGithubRepository) handleWorkflowTabLocking(workflowCount string) { + count, _ := strconv.Atoi(workflowCount) + if count == 0 { + m.skeleton.LockTab("workflow") + m.skeleton.LockTab("trigger") + } else { + m.skeleton.UnlockTab("workflow") + m.skeleton.UnlockTab("trigger") + } } + +// initSyncMsg is a message type used to trigger a UI update after the initial sync +type initSyncMsg struct{} diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index d94e333..e4abdf0 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -4,64 +4,104 @@ import ( "context" "errors" "fmt" + "strings" + "time" + "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" gu "github.com/termkit/gama/internal/github/usecase" - "github.com/termkit/gama/internal/terminal/handler/status" - hdltypes "github.com/termkit/gama/internal/terminal/handler/types" "github.com/termkit/gama/pkg/workflow" "github.com/termkit/skeleton" - "slices" - "strings" - "time" + "golang.org/x/exp/slices" ) +// ----------------------------------------------------------------------------- +// Model Definition +// ----------------------------------------------------------------------------- + type ModelGithubTrigger struct { + // Core dependencies skeleton *skeleton.Skeleton + github gu.UseCase - // current handler's properties - syncWorkflowContext context.Context - cancelSyncWorkflow context.CancelFunc + // UI Components + help help.Model + Keys githubTriggerKeyMap + tableTrigger table.Model + status *ModelStatus + textInput textinput.Model + + // Workflow state workflowContent *workflow.Pretty - tableReady bool - isTriggerable bool - optionInit bool - optionCursor int - optionValues []string - currentOption string selectedWorkflow string selectedRepositoryName string - triggerFocused bool + isTriggerable bool - // shared properties - selectedRepository *hdltypes.SelectedRepository + // Table state + tableReady bool - // use cases - github gu.UseCase + // Option state + optionInit bool + optionCursor int + optionValues []string + currentOption string - // keymap - Keys githubTriggerKeyMap + // Input state + triggerFocused bool - // models - help help.Model - status *status.ModelStatus - textInput textinput.Model - tableTrigger table.Model + // Context management + syncWorkflowContext context.Context + cancelSyncWorkflow context.CancelFunc + + // Shared state + selectedRepository *SelectedRepository } +// ----------------------------------------------------------------------------- +// Constructor & Initialization +// ----------------------------------------------------------------------------- + func SetupModelGithubTrigger(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubTrigger { - var tableRowsTrigger []table.Row + m := &ModelGithubTrigger{ + // Initialize core dependencies + skeleton: sk, + github: githubUseCase, + + // Initialize UI components + help: help.New(), + Keys: githubTriggerKeys, + status: SetupModelStatus(sk), + textInput: setupTriggerInput(), + tableTrigger: setupTriggerTable(), + + // Initialize state + selectedRepository: NewSelectedRepository(), + syncWorkflowContext: context.Background(), + cancelSyncWorkflow: func() {}, + } + + return m +} - tableTrigger := table.New( +func setupTriggerInput() textinput.Model { + ti := textinput.New() + ti.Blur() + ti.CharLimit = 72 + return ti +} + +func setupTriggerTable() table.Model { + t := table.New( table.WithColumns(tableColumnsTrigger), - table.WithRows(tableRowsTrigger), + table.WithRows([]table.Row{}), table.WithFocused(true), table.WithHeight(7), ) + // Apply styles s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). @@ -72,163 +112,176 @@ func SetupModelGithubTrigger(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *M Foreground(lipgloss.Color("229")). Background(lipgloss.Color("57")). Bold(false) - tableTrigger.SetStyles(s) - - ti := textinput.New() - ti.Blur() - ti.CharLimit = 72 + t.SetStyles(s) - modelStatus := status.SetupModelStatus(sk) - return &ModelGithubTrigger{ - skeleton: sk, - help: help.New(), - Keys: githubTriggerKeys, - github: githubUseCase, - selectedRepository: hdltypes.NewSelectedRepository(), - status: &modelStatus, - tableTrigger: tableTrigger, - textInput: ti, - syncWorkflowContext: context.Background(), - cancelSyncWorkflow: func() {}, - } + return t } +// ----------------------------------------------------------------------------- +// Bubbletea Model Implementation +// ----------------------------------------------------------------------------- + func (m *ModelGithubTrigger) Init() tea.Cmd { return tea.Batch(textinput.Blink) } func (m *ModelGithubTrigger) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - if m.selectedRepository.WorkflowName == "" { - m.status.Reset() - m.status.SetDefaultMessage("No workflow selected.") - m.fillTableWithEmptyMessage() - return m, nil - } - - if m.selectedRepository.WorkflowName != "" && (m.selectedRepository.WorkflowName != m.selectedWorkflow || m.selectedRepository.RepositoryName != m.selectedRepositoryName) { - m.tableReady = false - m.isTriggerable = false - m.triggerFocused = false - - m.cancelSyncWorkflow() // cancel previous sync workflow - - m.selectedWorkflow = m.selectedRepository.WorkflowName - m.selectedRepositoryName = m.selectedRepository.RepositoryName - m.syncWorkflowContext, m.cancelSyncWorkflow = context.WithCancel(context.Background()) - - go m.syncWorkflowContent(m.syncWorkflowContext) + if cmd := m.handleWorkflowChange(); cmd != nil { + return m, cmd } var cmds []tea.Cmd var cmd tea.Cmd - switch shadowMsg := msg.(type) { + // Handle key messages + switch msg := msg.(type) { case tea.KeyMsg: - switch shadowMsg.String() { - case "up": - if len(m.tableTrigger.Rows()) > 0 && !m.triggerFocused { - m.tableTrigger.MoveUp(1) - m.switchBetweenInputAndTable() - // delete msg key to prevent moving cursor - msg = tea.KeyMsg{Type: tea.KeyNull} - - m.optionInit = false - } - case "down": - if len(m.tableTrigger.Rows()) > 0 && !m.triggerFocused { - m.tableTrigger.MoveDown(1) - m.switchBetweenInputAndTable() - // delete msg key to prevent moving cursor - msg = tea.KeyMsg{Type: tea.KeyNull} - - m.optionInit = false - } - case "ctrl+r", "ctrl+R": - go m.syncWorkflowContent(m.syncWorkflowContext) - case "left": - if !m.triggerFocused { - m.optionCursor = max(m.optionCursor-1, 0) - } - case "right": - if !m.triggerFocused { - m.optionCursor = min(m.optionCursor+1, len(m.optionValues)-1) - } - case "tab": - if m.isTriggerable { - m.triggerFocused = !m.triggerFocused - if m.triggerFocused { - m.tableTrigger.Blur() - m.textInput.Blur() - m.showInformationIfAnyEmptyValue() - } else { - m.tableTrigger.Focus() - m.textInput.Focus() - } - } - case "enter", tea.KeyEnter.String(): - if m.triggerFocused && m.isTriggerable { - go m.triggerWorkflow() - } + cmd = m.handleKeyMsg(msg) + if cmd != nil { + cmds = append(cmds, cmd) } } - m.tableTrigger, cmd = m.tableTrigger.Update(msg) - cmds = append(cmds, cmd) - - m.textInput, cmd = m.textInput.Update(msg) - cmds = append(cmds, cmd) - - m.inputController(m.syncWorkflowContext) + // Update UI components + if cmd = m.updateUIComponents(msg); cmd != nil { + cmds = append(cmds, cmd) + } return m, tea.Batch(cmds...) } func (m *ModelGithubTrigger) View() string { - baseStyle := lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()).MarginLeft(1) - helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + return lipgloss.JoinVertical(lipgloss.Top, + m.renderTable(), + m.renderInputArea(), + m.status.View(), + m.renderHelp(), + ) +} - if m.triggerFocused { - baseStyle = baseStyle.BorderForeground(lipgloss.Color("240")) - } else { - baseStyle = baseStyle.BorderForeground(lipgloss.Color("#3b698f")) +// ----------------------------------------------------------------------------- +// Workflow Change Handling +// ----------------------------------------------------------------------------- + +func (m *ModelGithubTrigger) handleWorkflowChange() tea.Cmd { + if m.selectedRepository.WorkflowName == "" { + m.handleNoWorkflow() + return nil } - var tableWidth int - for _, t := range tableColumnsTrigger { - tableWidth += t.Width + if m.shouldSyncWorkflow() { + return m.initializeWorkflowSync() } - newTableColumns := tableColumnsTrigger - widthDiff := m.skeleton.GetTerminalWidth() - tableWidth - if widthDiff > 0 { - keyWidth := &newTableColumns[2].Width - valueWidth := &newTableColumns[4].Width + return nil +} - *valueWidth += widthDiff - 16 - if *valueWidth%2 == 0 { - *keyWidth = *valueWidth / 2 +func (m *ModelGithubTrigger) handleNoWorkflow() { + m.status.Reset() + m.status.SetDefaultMessage("No workflow selected.") + m.fillTableWithEmptyMessage() +} + +func (m *ModelGithubTrigger) shouldSyncWorkflow() bool { + return m.selectedRepository.WorkflowName != "" && + (m.selectedRepository.WorkflowName != m.selectedWorkflow || + m.selectedRepository.RepositoryName != m.selectedRepositoryName) +} + +func (m *ModelGithubTrigger) initializeWorkflowSync() tea.Cmd { + m.tableReady = false + m.isTriggerable = false + m.triggerFocused = false + + m.cancelSyncWorkflow() + + m.selectedWorkflow = m.selectedRepository.WorkflowName + m.selectedRepositoryName = m.selectedRepository.RepositoryName + m.syncWorkflowContext, m.cancelSyncWorkflow = context.WithCancel(context.Background()) + + go m.syncWorkflowContent(m.syncWorkflowContext) + return nil +} + +// ----------------------------------------------------------------------------- +// Key & Input Handling +// ----------------------------------------------------------------------------- + +func (m *ModelGithubTrigger) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { + switch msg.String() { + case "up": + return m.handleUpKey() + case "down": + return m.handleDownKey() + case "ctrl+r", "ctrl+R": + go m.syncWorkflowContent(m.syncWorkflowContext) + case "left": + m.handleLeftKey() + case "right": + m.handleRightKey() + case "tab": + m.handleTabKey() + case "enter": + if m.triggerFocused && m.isTriggerable { + go m.triggerWorkflow() } - m.tableTrigger.SetColumns(newTableColumns) - m.tableTrigger.SetHeight(m.skeleton.GetTerminalHeight() - 17) } + return nil +} - var selectedRow = m.tableTrigger.SelectedRow() - var selector = m.emptySelector() - if len(m.tableTrigger.Rows()) > 0 { - if selectedRow[1] == "input" { - selector = m.inputSelector() +func (m *ModelGithubTrigger) handleUpKey() tea.Cmd { + if len(m.tableTrigger.Rows()) > 0 && !m.triggerFocused { + m.tableTrigger.MoveUp(1) + m.switchBetweenInputAndTable() + m.optionInit = false + } + return nil +} + +func (m *ModelGithubTrigger) handleDownKey() tea.Cmd { + if len(m.tableTrigger.Rows()) > 0 && !m.triggerFocused { + m.tableTrigger.MoveDown(1) + m.switchBetweenInputAndTable() + m.optionInit = false + } + return nil +} + +func (m *ModelGithubTrigger) handleLeftKey() { + if !m.triggerFocused { + m.optionCursor = max(m.optionCursor-1, 0) + } +} + +func (m *ModelGithubTrigger) handleRightKey() { + if !m.triggerFocused { + m.optionCursor = min(m.optionCursor+1, len(m.optionValues)-1) + } +} + +func (m *ModelGithubTrigger) handleTabKey() { + if m.isTriggerable { + m.triggerFocused = !m.triggerFocused + if m.triggerFocused { + m.tableTrigger.Blur() + m.textInput.Blur() + m.showInformationIfAnyEmptyValue() } else { - selector = m.optionSelector() + m.tableTrigger.Focus() + m.textInput.Focus() } } - - return lipgloss.JoinVertical(lipgloss.Top, - baseStyle.Render(m.tableTrigger.View()), lipgloss.JoinHorizontal(lipgloss.Top, selector, m.triggerButton()), - m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } +// ----------------------------------------------------------------------------- +// Input Management +// ----------------------------------------------------------------------------- + func (m *ModelGithubTrigger) switchBetweenInputAndTable() { - var selectedRow = m.tableTrigger.SelectedRow() + selectedRow := m.tableTrigger.SelectedRow() + if len(selectedRow) == 0 { + return + } if selectedRow[1] == "input" || selectedRow[1] == "bool" { m.textInput.Focus() @@ -237,157 +290,84 @@ func (m *ModelGithubTrigger) switchBetweenInputAndTable() { m.textInput.Blur() m.tableTrigger.Focus() } - m.textInput.SetValue(m.tableTrigger.SelectedRow()[4]) + + m.textInput.SetValue(selectedRow[4]) m.textInput.SetCursor(len(m.textInput.Value())) } -func (m *ModelGithubTrigger) inputController(_ context.Context) { +func (m *ModelGithubTrigger) inputController() { if m.workflowContent == nil { return } - if len(m.tableTrigger.Rows()) > 0 { - var selectedRow = m.tableTrigger.SelectedRow() - if len(selectedRow) == 0 { - return - } - - switch selectedRow[1] { - case "choice": - var optionValues []string - for _, choice := range m.workflowContent.Choices { - if fmt.Sprintf("%d", choice.ID) == selectedRow[0] { - optionValues = append(optionValues, choice.Values...) - } - } - m.optionValues = optionValues - if !m.optionInit { - for i, option := range m.optionValues { - if option == selectedRow[4] { - m.optionCursor = i - } - } - } - m.optionInit = true - case "bool": - var optionValues []string - for _, choice := range m.workflowContent.Boolean { - if fmt.Sprintf("%d", choice.ID) == selectedRow[0] { - optionValues = append(optionValues, choice.Values...) - } - } - m.optionValues = optionValues - if !m.optionInit { - for i, option := range m.optionValues { - if option == selectedRow[4] { - m.optionCursor = i - } - } - } - m.optionInit = true - default: - m.optionValues = nil - m.optionCursor = 0 - - if !m.triggerFocused { - m.textInput.Focus() - } - } + selectedRow := m.tableTrigger.SelectedRow() + if len(selectedRow) == 0 { + return } - for i, choice := range m.workflowContent.Choices { - var selectedRow = m.tableTrigger.SelectedRow() - var rows = m.tableTrigger.Rows() + switch selectedRow[1] { + case "choice", "bool": + m.handleChoiceInput(selectedRow) + default: + m.handleTextInput() + } +} - if len(selectedRow) == 0 || len(rows) == 0 { - return - } - if fmt.Sprintf("%d", choice.ID) == selectedRow[0] { - m.workflowContent.Choices[i].SetValue(m.optionValues[m.optionCursor]) +func (m *ModelGithubTrigger) handleChoiceInput(row []string) { + var optionValues []string + if row[1] == "choice" { + optionValues = m.getChoiceValues(row[0]) + } else { + optionValues = m.getBooleanValues(row[0]) + } - for i, row := range rows { - if row[0] == selectedRow[0] { - rows[i][4] = m.optionValues[m.optionCursor] - } + m.optionValues = optionValues + if !m.optionInit { + for i, option := range m.optionValues { + if option == row[4] { + m.optionCursor = i } - - m.tableTrigger.SetRows(rows) } } + m.optionInit = true - if m.workflowContent.Boolean != nil { - for i, boolean := range m.workflowContent.Boolean { - var selectedRow = m.tableTrigger.SelectedRow() - var rows = m.tableTrigger.Rows() - if len(selectedRow) == 0 || len(rows) == 0 { - return - } - if fmt.Sprintf("%d", boolean.ID) == selectedRow[0] { - m.workflowContent.Boolean[i].SetValue(m.optionValues[m.optionCursor]) + state := &InputState{ + Type: row[1], + Options: m.optionValues, + Cursor: m.optionCursor, + } + m.updateInputState(state) +} - for i, row := range rows { - if row[0] == selectedRow[0] { - rows[i][4] = m.optionValues[m.optionCursor] - } - } +func (m *ModelGithubTrigger) handleTextInput() { + m.optionValues = nil + m.optionCursor = 0 - m.tableTrigger.SetRows(rows) - } - } + if !m.triggerFocused { + m.textInput.Focus() } if m.textInput.Focused() { - if strings.HasPrefix(m.textInput.Value(), " ") { - m.textInput.SetValue("") - } - - var selectedRow = m.tableTrigger.SelectedRow() - var rows = m.tableTrigger.Rows() - if len(selectedRow) == 0 || len(rows) == 0 { - return - } - - for i, input := range m.workflowContent.Inputs { - if fmt.Sprintf("%d", input.ID) == selectedRow[0] { - m.textInput.Placeholder = input.Default - m.workflowContent.Inputs[i].SetValue(m.textInput.Value()) - - for i, row := range rows { - if row[0] == selectedRow[0] { - rows[i][4] = m.textInput.Value() - } - } - - m.tableTrigger.SetRows(rows) - } - } - - for i, keyVal := range m.workflowContent.KeyVals { - if fmt.Sprintf("%d", keyVal.ID) == selectedRow[0] { - m.textInput.Placeholder = keyVal.Default - m.workflowContent.KeyVals[i].SetValue(m.textInput.Value()) - - for i, row := range rows { - if row[0] == selectedRow[0] { - rows[i][4] = m.textInput.Value() - } - } - - m.tableTrigger.SetRows(rows) - } + state := &InputState{ + Type: "input", + Value: m.textInput.Value(), + IsFocused: true, } + m.updateInputState(state) } } +// ----------------------------------------------------------------------------- +// Workflow Content Management +// ----------------------------------------------------------------------------- + func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { defer m.skeleton.TriggerUpdate() m.status.Reset() - m.status.SetProgressMessage( - fmt.Sprintf("[%s@%s] Fetching workflow contents...", - m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + m.status.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching workflow contents...", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) - // reset table rows m.tableTrigger.SetRows([]table.Row{}) workflowContent, err := m.github.InspectWorkflow(ctx, gu.InspectWorkflowInput{ @@ -395,35 +375,48 @@ func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { Branch: m.selectedRepository.BranchName, WorkflowFile: m.selectedWorkflow, }) - if errors.Is(err, context.Canceled) { - return - } else if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Workflow contents cannot be fetched") + + if err != nil { + m.handleWorkflowError(err) return } - if workflowContent.Workflow == nil { + m.processWorkflowContent(workflowContent) +} + +func (m *ModelGithubTrigger) processWorkflowContent(content *gu.InspectWorkflowOutput) { + if content.Workflow == nil { m.status.SetError(errors.New("workflow contents cannot be empty")) m.status.SetErrorMessage("You have no workflow contents") return } - m.workflowContent = workflowContent.Workflow + m.workflowContent = content.Workflow + m.updateTriggerTable() + m.finalizeWorkflowUpdate() +} + +// ----------------------------------------------------------------------------- +// Table Management +// ----------------------------------------------------------------------------- - var tableRowsTrigger []table.Row +func (m *ModelGithubTrigger) updateTriggerTable() { + var rows []table.Row + + // Add key-value inputs for _, keyVal := range m.workflowContent.KeyVals { - tableRowsTrigger = append(tableRowsTrigger, table.Row{ + rows = append(rows, table.Row{ fmt.Sprintf("%d", keyVal.ID), - "input", // json type + "input", keyVal.Key, keyVal.Default, keyVal.Value, }) } + // Add choice inputs for _, choice := range m.workflowContent.Choices { - tableRowsTrigger = append(tableRowsTrigger, table.Row{ + rows = append(rows, table.Row{ fmt.Sprintf("%d", choice.ID), "choice", choice.Key, @@ -432,8 +425,9 @@ func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { }) } + // Add regular inputs for _, input := range m.workflowContent.Inputs { - tableRowsTrigger = append(tableRowsTrigger, table.Row{ + rows = append(rows, table.Row{ fmt.Sprintf("%d", input.ID), "input", input.Key, @@ -442,8 +436,9 @@ func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { }) } + // Add boolean inputs for _, boolean := range m.workflowContent.Boolean { - tableRowsTrigger = append(tableRowsTrigger, table.Row{ + rows = append(rows, table.Row{ fmt.Sprintf("%d", boolean.ID), "bool", boolean.Key, @@ -452,15 +447,25 @@ func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { }) } - m.tableTrigger.SetRows(tableRowsTrigger) + m.tableTrigger.SetRows(rows) m.sortTableItemsByName() +} + +func (m *ModelGithubTrigger) sortTableItemsByName() { + rows := m.tableTrigger.Rows() + slices.SortFunc(rows, func(a, b table.Row) int { + return strings.Compare(a[2], b[2]) + }) + m.tableTrigger.SetRows(rows) +} + +func (m *ModelGithubTrigger) finalizeWorkflowUpdate() { m.tableTrigger.SetCursor(0) m.optionCursor = 0 m.optionValues = nil m.triggerFocused = false m.tableTrigger.Focus() - // reset input value m.textInput.SetCursor(0) m.textInput.SetValue("") m.textInput.Placeholder = "" @@ -468,38 +473,127 @@ func (m *ModelGithubTrigger) syncWorkflowContent(ctx context.Context) { m.tableReady = true m.isTriggerable = true - if len(workflowContent.Workflow.KeyVals) == 0 && - len(workflowContent.Workflow.Choices) == 0 && - len(workflowContent.Workflow.Inputs) == 0 { - m.fillTableWithEmptyMessage() - m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] Workflow doesn't contain options but still triggerable", - m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + if m.hasNoWorkflowOptions() { + m.handleEmptyWorkflow() } else { m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow contents fetched.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) } } -func (m *ModelGithubTrigger) fillTableWithEmptyMessage() { - var rows []table.Row - for i := 0; i < 100; i++ { - idx := fmt.Sprintf("%d", i) - rows = append(rows, table.Row{ - idx, "EMPTY", "EMPTY", "EMPTY", "No workflow input found", - }) +// ----------------------------------------------------------------------------- +// Trigger Logic +// ----------------------------------------------------------------------------- + +func (m *ModelGithubTrigger) triggerWorkflow() { + if m.triggerFocused { + m.fillEmptyValuesWithDefault() } - m.tableTrigger.SetRows(rows) - m.tableTrigger.SetCursor(0) + m.status.SetProgressMessage(fmt.Sprintf("[%s@%s]:[%s] Triggering workflow...", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName, m.selectedWorkflow)) + + if m.workflowContent == nil { + m.status.SetErrorMessage("Workflow contents cannot be empty") + return + } + + content, err := m.workflowContent.ToJson() + if err != nil { + m.status.SetError(err) + m.status.SetErrorMessage("Workflow contents cannot be converted to JSON") + return + } + + _, err = m.github.TriggerWorkflow(context.Background(), gu.TriggerWorkflowInput{ + Repository: m.selectedRepository.RepositoryName, + Branch: m.selectedRepository.BranchName, + WorkflowFile: m.selectedWorkflow, + Content: content, + }) + + if err != nil { + m.status.SetError(err) + m.status.SetErrorMessage("Workflow cannot be triggered") + return + } + + m.handleSuccessfulTrigger() } -func (m *ModelGithubTrigger) showInformationIfAnyEmptyValue() { - for _, row := range m.tableTrigger.Rows() { - if row[4] == "" { - m.status.SetDefaultMessage("Info: You have empty values. These values uses their default values.") - return +func (m *ModelGithubTrigger) handleSuccessfulTrigger() { + m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s]:[%s] Workflow triggered.", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName, m.selectedWorkflow)) + + m.status.SetProgressMessage("Switching to workflow history tab...") + time.Sleep(2000 * time.Millisecond) + + m.resetTriggerState() + m.switchToHistoryTab() +} + +// ----------------------------------------------------------------------------- +// UI Rendering +// ----------------------------------------------------------------------------- + +func (m *ModelGithubTrigger) renderTable() string { + baseStyle := lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + MarginLeft(1) + + if m.triggerFocused { + baseStyle = baseStyle.BorderForeground(lipgloss.Color("240")) + } else { + baseStyle = baseStyle.BorderForeground(lipgloss.Color("#3b698f")) + } + + m.updateTableDimensions() + return baseStyle.Render(m.tableTrigger.View()) +} + +func (m *ModelGithubTrigger) renderInputArea() string { + var selectedRow = m.tableTrigger.SelectedRow() + var selector = m.emptySelector() + + if len(m.tableTrigger.Rows()) > 0 { + if selectedRow[1] == "input" { + selector = m.inputSelector() + } else { + selector = m.optionSelector() + } + } + + return lipgloss.JoinHorizontal(lipgloss.Top, selector, m.triggerButton()) +} + +func (m *ModelGithubTrigger) renderHelp() string { + helpStyle := WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + return helpStyle.Render(m.help.View(m.Keys)) +} + +// ----------------------------------------------------------------------------- +// Helper Functions - Selectors +// ----------------------------------------------------------------------------- + +func (m *ModelGithubTrigger) emptySelector() string { + return m.getSelectorStyle().Render("") +} + +func (m *ModelGithubTrigger) inputSelector() string { + return m.getSelectorStyle().Render(m.textInput.View()) +} + +func (m *ModelGithubTrigger) optionSelector() string { + var processedValues []string + for i, option := range m.optionValues { + if i == m.optionCursor { + processedValues = append(processedValues, m.getSelectedOptionStyle().Render(option)) + } else { + processedValues = append(processedValues, m.getUnselectedOptionStyle().Render(option)) } } + + return m.getSelectorStyle().Render(lipgloss.JoinHorizontal(lipgloss.Left, processedValues...)) } func (m *ModelGithubTrigger) triggerButton() string { @@ -510,7 +604,8 @@ func (m *ModelGithubTrigger) triggerButton() string { Align(lipgloss.Center) if m.triggerFocused { - button = button.BorderForeground(lipgloss.Color("#399adb")). + button = button. + BorderForeground(lipgloss.Color("#399adb")). Foreground(lipgloss.Color("#399adb")). BorderStyle(lipgloss.DoubleBorder()) } @@ -518,10 +613,127 @@ func (m *ModelGithubTrigger) triggerButton() string { return button.Render("Trigger") } +// ----------------------------------------------------------------------------- +// Helper Functions - Styles +// ----------------------------------------------------------------------------- + +func (m *ModelGithubTrigger) getSelectorStyle() lipgloss.Style { + return lipgloss.NewStyle(). + Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#3b698f")). + Padding(0, 1). + Width(m.skeleton.GetTerminalWidth() - 18). + MarginLeft(1) +} + +func (m *ModelGithubTrigger) getSelectedOptionStyle() lipgloss.Style { + return lipgloss.NewStyle(). + Foreground(lipgloss.Color("120")). + Padding(0, 1) +} + +func (m *ModelGithubTrigger) getUnselectedOptionStyle() lipgloss.Style { + return lipgloss.NewStyle(). + Foreground(lipgloss.Color("140")). + Padding(0, 1) +} + +// ----------------------------------------------------------------------------- +// Helper Functions - Value Management +// ----------------------------------------------------------------------------- + +func (m *ModelGithubTrigger) getChoiceValues(id string) []string { + for _, choice := range m.workflowContent.Choices { + if fmt.Sprintf("%d", choice.ID) == id { + return choice.Values + } + } + return nil +} + +func (m *ModelGithubTrigger) getBooleanValues(id string) []string { + for _, boolean := range m.workflowContent.Boolean { + if fmt.Sprintf("%d", boolean.ID) == id { + return boolean.Values + } + } + return nil +} + +func (m *ModelGithubTrigger) updateChoiceValue(row []string, value string) { + rows := m.tableTrigger.Rows() + for i, r := range rows { + if r[0] == row[0] { + rows[i][4] = value + } + } + m.tableTrigger.SetRows(rows) + + if row[1] == "choice" { + m.updateWorkflowChoiceValue(row[0]) + } else { + m.updateWorkflowBooleanValue(row[0]) + } +} + +func (m *ModelGithubTrigger) updateWorkflowChoiceValue(id string) { + for i, choice := range m.workflowContent.Choices { + if fmt.Sprintf("%d", choice.ID) == id { + m.workflowContent.Choices[i].SetValue(m.optionValues[m.optionCursor]) + break + } + } +} + +func (m *ModelGithubTrigger) updateWorkflowBooleanValue(id string) { + for i, boolean := range m.workflowContent.Boolean { + if fmt.Sprintf("%d", boolean.ID) == id { + m.workflowContent.Boolean[i].SetValue(m.optionValues[m.optionCursor]) + break + } + } +} + +func (m *ModelGithubTrigger) updateTextInputValue(row []string, value string) { + if strings.HasPrefix(value, " ") { + return + } + + rows := m.tableTrigger.Rows() + for i, r := range rows { + if r[0] == row[0] { + rows[i][4] = value + } + } + m.tableTrigger.SetRows(rows) + + m.updateWorkflowInputValue(row) +} + +func (m *ModelGithubTrigger) updateWorkflowInputValue(row []string) { + for i, input := range m.workflowContent.Inputs { + if fmt.Sprintf("%d", input.ID) == row[0] { + m.textInput.Placeholder = input.Default + m.workflowContent.Inputs[i].SetValue(m.textInput.Value()) + return + } + } + + for i, keyVal := range m.workflowContent.KeyVals { + if fmt.Sprintf("%d", keyVal.ID) == row[0] { + m.textInput.Placeholder = keyVal.Default + m.workflowContent.KeyVals[i].SetValue(m.textInput.Value()) + return + } + } +} + +// ----------------------------------------------------------------------------- +// Helper Functions - State Management +// ----------------------------------------------------------------------------- + func (m *ModelGithubTrigger) fillEmptyValuesWithDefault() { if m.workflowContent == nil { - m.status.SetError(errors.New("workflow contents cannot be empty")) - m.status.SetErrorMessage("You have no workflow contents") return } @@ -533,11 +745,14 @@ func (m *ModelGithubTrigger) fillEmptyValuesWithDefault() { } m.tableTrigger.SetRows(rows) + m.fillWorkflowEmptyValues() +} + +func (m *ModelGithubTrigger) fillWorkflowEmptyValues() { for i, choice := range m.workflowContent.Choices { if choice.Value == "" { m.workflowContent.Choices[i].SetValue(choice.Default) } - } for i, input := range m.workflowContent.Inputs { @@ -559,108 +774,131 @@ func (m *ModelGithubTrigger) fillEmptyValuesWithDefault() { } } -func (m *ModelGithubTrigger) triggerWorkflow() { - if m.triggerFocused { - m.fillEmptyValuesWithDefault() - } +func (m *ModelGithubTrigger) resetTriggerState() { + m.workflowContent = nil + m.selectedWorkflow = "" + m.currentOption = "" + m.optionValues = nil + m.selectedRepositoryName = "" +} - m.status.SetProgressMessage(fmt.Sprintf("[%s@%s]:[%s] Triggering workflow...", - m.selectedRepository.RepositoryName, m.selectedRepository.BranchName, m.selectedWorkflow)) +func (m *ModelGithubTrigger) switchToHistoryTab() { + m.skeleton.TriggerUpdateWithMsg(workflowHistoryUpdateMsg{time.Second * 3}) + m.skeleton.SetActivePage("history") +} - if m.workflowContent == nil { - m.status.SetErrorMessage("Workflow contents cannot be empty") - return +func (m *ModelGithubTrigger) hasNoWorkflowOptions() bool { + return len(m.workflowContent.KeyVals) == 0 && + len(m.workflowContent.Choices) == 0 && + len(m.workflowContent.Inputs) == 0 +} + +func (m *ModelGithubTrigger) handleEmptyWorkflow() { + m.fillTableWithEmptyMessage() + m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] Workflow doesn't contain options but still triggerable", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) +} + +func (m *ModelGithubTrigger) fillTableWithEmptyMessage() { + var rows []table.Row + for i := 0; i < 100; i++ { + idx := fmt.Sprintf("%d", i) + rows = append(rows, table.Row{ + idx, "EMPTY", "EMPTY", "EMPTY", "No workflow input found", + }) } - content, err := m.workflowContent.ToJson() - if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Workflow contents cannot be converted to JSON") - return + m.tableTrigger.SetRows(rows) + m.tableTrigger.SetCursor(0) +} + +func (m *ModelGithubTrigger) showInformationIfAnyEmptyValue() { + for _, row := range m.tableTrigger.Rows() { + if row[4] == "" { + m.status.SetDefaultMessage("Info: You have empty values. These values uses their default values.") + return + } } +} - _, err = m.github.TriggerWorkflow(context.Background(), gu.TriggerWorkflowInput{ - Repository: m.selectedRepository.RepositoryName, - Branch: m.selectedRepository.BranchName, - WorkflowFile: m.selectedWorkflow, - Content: content, - }) - if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Workflow cannot be triggered") +func (m *ModelGithubTrigger) handleWorkflowError(err error) { + if errors.Is(err, context.Canceled) { return } + m.status.SetError(err) + m.status.SetErrorMessage("Workflow contents cannot be fetched") +} - m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s]:[%s] Workflow triggered.", - m.selectedRepository.RepositoryName, m.selectedRepository.BranchName, m.selectedWorkflow)) +// ----------------------------------------------------------------------------- +// Helper Functions - Table Dimensions +// ----------------------------------------------------------------------------- - m.skeleton.TriggerUpdate() - m.status.SetProgressMessage("Switching to workflow history tab...") - time.Sleep(2000 * time.Millisecond) +func (m *ModelGithubTrigger) updateTableDimensions() { + var tableWidth int + for _, t := range tableColumnsTrigger { + tableWidth += t.Width + } - // move these operations under new function named "resetTabSettings" - m.workflowContent = nil // reset workflow content - m.selectedWorkflow = "" // reset selected workflow - m.currentOption = "" // reset current option - m.optionValues = nil // reset option values - m.selectedRepositoryName = "" // reset selected repository name + newTableColumns := tableColumnsTrigger + widthDiff := m.skeleton.GetTerminalWidth() - tableWidth + if widthDiff > 0 { + keyWidth := &newTableColumns[2].Width + valueWidth := &newTableColumns[4].Width - m.skeleton.TriggerUpdateWithMsg(workflowHistoryUpdateMsg{time.Second * 3}) // update workflow history - m.skeleton.SetActivePage("history") // switch tab to workflow history + *valueWidth += widthDiff - 16 + if *valueWidth%2 == 0 { + *keyWidth = *valueWidth / 2 + } + m.tableTrigger.SetColumns(newTableColumns) + m.tableTrigger.SetHeight(m.skeleton.GetTerminalHeight() - 17) + } } -func (m *ModelGithubTrigger) emptySelector() string { - // Define window style - windowStyle := lipgloss.NewStyle(). - Border(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("#3b698f")). - Padding(0, 1). - Width(m.skeleton.GetTerminalWidth() - 18).MarginLeft(1) - - return windowStyle.Render("") -} +// ----------------------------------------------------------------------------- +// UI Component Updates +// ----------------------------------------------------------------------------- -func (m *ModelGithubTrigger) inputSelector() string { - // Define window style - windowStyle := lipgloss.NewStyle(). - Border(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("#3b698f")). - Padding(0, 1). - Width(m.skeleton.GetTerminalWidth() - 18).MarginLeft(1) +func (m *ModelGithubTrigger) updateUIComponents(msg tea.Msg) tea.Cmd { + var cmds []tea.Cmd + var cmd tea.Cmd - return windowStyle.Render(m.textInput.View()) -} + // Update text input + m.textInput, cmd = m.textInput.Update(msg) + cmds = append(cmds, cmd) -// optionSelector renders the options list -// TODO: Make this dynamic limited&sized. -func (m *ModelGithubTrigger) optionSelector() string { - // Define window style - windowStyle := lipgloss.NewStyle(). - Border(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("#3b698f")). - Padding(0, 1). - Width(m.skeleton.GetTerminalWidth() - 18).MarginLeft(1) + // Update table + m.tableTrigger, cmd = m.tableTrigger.Update(msg) + cmds = append(cmds, cmd) - // Define styles for selected and unselected options - selectedOptionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("120")).Padding(0, 1) - unselectedOptionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("140")).Padding(0, 1) + // Handle input controller + m.inputController() - var processedValues []string - for i, option := range m.optionValues { - if i == m.optionCursor { - processedValues = append(processedValues, selectedOptionStyle.Render(option)) - } else { - processedValues = append(processedValues, unselectedOptionStyle.Render(option)) - } - } + return tea.Batch(cmds...) +} - return windowStyle.Render(lipgloss.JoinHorizontal(lipgloss.Left, processedValues...)) +// ----------------------------------------------------------------------------- +// Input & Table State Management +// ----------------------------------------------------------------------------- + +type InputState struct { + Type string // "input", "choice", "bool" + Value string + Default string + Options []string + Cursor int + IsFocused bool } -func (m *ModelGithubTrigger) sortTableItemsByName() { - rows := m.tableTrigger.Rows() - slices.SortFunc(rows, func(a, b table.Row) int { - return strings.Compare(a[2], b[2]) - }) - m.tableTrigger.SetRows(rows) +func (m *ModelGithubTrigger) updateInputState(state *InputState) { + row := m.tableTrigger.SelectedRow() + if len(row) == 0 { + return + } + + switch state.Type { + case "choice", "bool": + m.updateChoiceValue(row, state.Options[state.Cursor]) + case "input": + m.updateTextInputValue(row, state.Value) + } } diff --git a/internal/terminal/handler/ghworkflow.go b/internal/terminal/handler/ghworkflow.go index 299b3b9..621a6cb 100644 --- a/internal/terminal/handler/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow.go @@ -4,55 +4,93 @@ import ( "context" "errors" "fmt" + "sort" + "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textinput" - "github.com/termkit/gama/internal/terminal/handler/status" - hdltypes "github.com/termkit/gama/internal/terminal/handler/types" - "github.com/termkit/skeleton" - "sort" - tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" gu "github.com/termkit/gama/internal/github/usecase" + "github.com/termkit/skeleton" ) +// ----------------------------------------------------------------------------- +// Model Definition +// ----------------------------------------------------------------------------- + type ModelGithubWorkflow struct { + // Core dependencies skeleton *skeleton.Skeleton - // current handler's properties + github gu.UseCase + + // UI Components + help help.Model + keys githubWorkflowKeyMap + tableTriggerableWorkflow table.Model + status *ModelStatus + textInput textinput.Model + + // Table state + tableReady bool + lastRepository string + mainBranch string + + // Context management syncTriggerableWorkflowsContext context.Context cancelSyncTriggerableWorkflows context.CancelFunc - tableReady bool - lastRepository string - mainBranch string - // shared properties - selectedRepository *hdltypes.SelectedRepository + // Shared state + selectedRepository *SelectedRepository +} + +// ----------------------------------------------------------------------------- +// Constructor & Initialization +// ----------------------------------------------------------------------------- - // use cases - github gu.UseCase +func SetupModelGithubWorkflow(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflow { + m := &ModelGithubWorkflow{ + // Initialize core dependencies + skeleton: sk, + github: githubUseCase, + + // Initialize UI components + help: help.New(), + keys: githubWorkflowKeys, + status: SetupModelStatus(sk), + textInput: setupBranchInput(), + + // Initialize state + selectedRepository: NewSelectedRepository(), + syncTriggerableWorkflowsContext: context.Background(), + cancelSyncTriggerableWorkflows: func() {}, + } - // keymap - keys githubWorkflowKeyMap + // Setup table + m.tableTriggerableWorkflow = setupWorkflowTable() - // models - help help.Model - tableTriggerableWorkflow table.Model - status *status.ModelStatus - textInput textinput.Model + return m } -func SetupModelGithubWorkflow(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflow { - var tableRowsTriggerableWorkflow []table.Row +func setupBranchInput() textinput.Model { + ti := textinput.New() + ti.Focus() + ti.CharLimit = 128 + ti.Placeholder = "Type to switch branch" + ti.ShowSuggestions = true + return ti +} - tableTriggerableWorkflow := table.New( +func setupWorkflowTable() table.Model { + t := table.New( table.WithColumns(tableColumnsWorkflow), - table.WithRows(tableRowsTriggerableWorkflow), + table.WithRows([]table.Row{}), table.WithFocused(true), table.WithHeight(7), ) + // Apply styles s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). @@ -63,232 +101,311 @@ func SetupModelGithubWorkflow(sk *skeleton.Skeleton, githubUseCase gu.UseCase) * Foreground(lipgloss.Color("229")). Background(lipgloss.Color("57")). Bold(false) - tableTriggerableWorkflow.SetStyles(s) - - tableTriggerableWorkflow.KeyMap = table.KeyMap{ - LineUp: key.NewBinding( - key.WithKeys("up"), - ), - LineDown: key.NewBinding( - key.WithKeys("down"), - ), + t.SetStyles(s) + + // Set keymap + t.KeyMap = table.KeyMap{ + LineUp: key.NewBinding(key.WithKeys("up"), key.WithHelp("↑", "up")), + LineDown: key.NewBinding(key.WithKeys("down"), key.WithHelp("↓", "down")), + PageUp: key.NewBinding(key.WithKeys("pgup"), key.WithHelp("pgup", "page up")), + PageDown: key.NewBinding(key.WithKeys("pgdown"), key.WithHelp("pgdn", "page down")), + GotoTop: key.NewBinding(key.WithKeys("home"), key.WithHelp("home", "go to start")), + GotoBottom: key.NewBinding(key.WithKeys("end"), key.WithHelp("end", "go to end")), } - ti := textinput.New() - ti.Focus() - ti.CharLimit = 128 - ti.Placeholder = "Type to switch branch" - ti.ShowSuggestions = true - - modelStatus := status.SetupModelStatus(sk) - - return &ModelGithubWorkflow{ - skeleton: sk, - help: help.New(), - keys: githubWorkflowKeys, - github: githubUseCase, - status: &modelStatus, - tableTriggerableWorkflow: tableTriggerableWorkflow, - selectedRepository: hdltypes.NewSelectedRepository(), - syncTriggerableWorkflowsContext: context.Background(), - cancelSyncTriggerableWorkflows: func() {}, - textInput: ti, - } + return t } +// ----------------------------------------------------------------------------- +// Bubbletea Model Implementation +// ----------------------------------------------------------------------------- + func (m *ModelGithubWorkflow) Init() tea.Cmd { return nil } func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + if cmd := m.handleRepositoryChange(); cmd != nil { + return m, cmd + } + var cmds []tea.Cmd var cmd tea.Cmd + // Update text input and handle branch selection + m.textInput, cmd = m.textInput.Update(msg) + cmds = append(cmds, cmd) + m.handleBranchSelection() + + // Update table and handle workflow selection + m.tableTriggerableWorkflow, cmd = m.tableTriggerableWorkflow.Update(msg) + cmds = append(cmds, cmd) + m.handleTableInputs() + + return m, tea.Batch(cmds...) +} + +func (m *ModelGithubWorkflow) View() string { + return lipgloss.JoinVertical(lipgloss.Top, + m.renderTable(), + m.renderBranchInput(), + m.status.View(), + m.renderHelp(), + ) +} + +// ----------------------------------------------------------------------------- +// Repository Change Handling +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflow) handleRepositoryChange() tea.Cmd { if m.lastRepository != m.selectedRepository.RepositoryName { - m.tableReady = false // reset table ready status - m.cancelSyncTriggerableWorkflows() // cancel previous sync - m.syncTriggerableWorkflowsContext, m.cancelSyncTriggerableWorkflows = context.WithCancel(context.Background()) + m.tableReady = false + m.cancelSyncTriggerableWorkflows() m.lastRepository = m.selectedRepository.RepositoryName m.mainBranch = m.selectedRepository.BranchName + m.syncTriggerableWorkflowsContext, m.cancelSyncTriggerableWorkflows = context.WithCancel(context.Background()) + go m.syncTriggerableWorkflows(m.syncTriggerableWorkflowsContext) go m.syncBranches(m.syncTriggerableWorkflowsContext) } + return nil +} - var selectedBranch = m.textInput.Value() - if selectedBranch != "" { - var isBranchExist bool - for _, branch := range m.textInput.AvailableSuggestions() { - if branch == selectedBranch { - isBranchExist = true - m.selectedRepository.BranchName = selectedBranch - break - } - } +// ----------------------------------------------------------------------------- +// Branch Selection & Management +// ----------------------------------------------------------------------------- - if !isBranchExist { - m.status.SetErrorMessage(fmt.Sprintf("Branch %s is not exist", selectedBranch)) - m.skeleton.LockTabsToTheRight() - } else { - m.skeleton.UnlockTabs() - } - } +func (m *ModelGithubWorkflow) handleBranchSelection() { + selectedBranch := m.textInput.Value() if selectedBranch == "" { m.selectedRepository.BranchName = m.mainBranch m.skeleton.UnlockTabs() + return } - m.textInput, cmd = m.textInput.Update(msg) - cmds = append(cmds, cmd) + if m.isBranchValid(selectedBranch) { + m.selectedRepository.BranchName = selectedBranch + m.skeleton.UnlockTabs() + } else { + m.status.SetErrorMessage(fmt.Sprintf("Branch %s does not exist", selectedBranch)) + m.skeleton.LockTabsToTheRight() + } +} - m.tableTriggerableWorkflow, cmd = m.tableTriggerableWorkflow.Update(msg) - cmds = append(cmds, cmd) +func (m *ModelGithubWorkflow) isBranchValid(branch string) bool { + for _, suggestion := range m.textInput.AvailableSuggestions() { + if suggestion == branch { + return true + } + } + return false +} - m.handleTableInputs(m.syncTriggerableWorkflowsContext) // update table operations +// ----------------------------------------------------------------------------- +// Workflow Sync & Management +// ----------------------------------------------------------------------------- - return m, tea.Batch(cmds...) -} +func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { + defer m.skeleton.TriggerUpdate() -func (m *ModelGithubWorkflow) View() string { - var style = lipgloss.NewStyle(). - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) + m.initializeSyncState() + workflows, err := m.fetchTriggerableWorkflows(ctx) + if err != nil { + return + } - helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + m.processWorkflows(workflows) +} - termWidth := m.skeleton.GetTerminalWidth() - termHeight := m.skeleton.GetTerminalHeight() +func (m *ModelGithubWorkflow) initializeSyncState() { + m.status.Reset() + m.status.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching triggerable workflows...", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + m.tableTriggerableWorkflow.SetRows([]table.Row{}) +} - var tableWidth int - for _, t := range tableColumnsWorkflow { - tableWidth += t.Width - } +func (m *ModelGithubWorkflow) fetchTriggerableWorkflows(ctx context.Context) (*gu.GetTriggerableWorkflowsOutput, error) { + workflows, err := m.github.GetTriggerableWorkflows(ctx, gu.GetTriggerableWorkflowsInput{ + Repository: m.selectedRepository.RepositoryName, + Branch: m.selectedRepository.BranchName, + }) - newTableColumns := tableColumnsWorkflow - widthDiff := termWidth - tableWidth - if widthDiff > 0 { - newTableColumns[1].Width += widthDiff - 10 - m.tableTriggerableWorkflow.SetColumns(newTableColumns) - m.tableTriggerableWorkflow.SetHeight(termHeight - 17) + if err != nil { + if !errors.Is(err, context.Canceled) { + m.status.SetError(err) + m.status.SetErrorMessage("Triggerable workflows cannot be listed") + } + return nil, err } - return lipgloss.JoinVertical(lipgloss.Top, - style.Render(m.tableTriggerableWorkflow.View()), - m.viewSearchBar(), - m.status.View(), - helpWindowStyle.Render(m.ViewHelp()), - ) + return workflows, nil } -func (m *ModelGithubWorkflow) viewSearchBar() string { - // Define window style - windowStyle := lipgloss.NewStyle(). - Border(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("#3b698f")). - Padding(0, 1). - Width(m.skeleton.GetTerminalWidth() - 6).MarginLeft(1) - - if len(m.textInput.AvailableSuggestions()) > 0 && m.textInput.Value() == "" { - var mainBranch = m.mainBranch - m.textInput.Placeholder = "Type to switch branch (default: " + mainBranch + ")" +func (m *ModelGithubWorkflow) processWorkflows(workflows *gu.GetTriggerableWorkflowsOutput) { + if len(workflows.TriggerableWorkflows) == 0 { + m.handleEmptyWorkflows() + return } - return windowStyle.Render(m.textInput.View()) + m.updateWorkflowTable(workflows.TriggerableWorkflows) + m.finalizeUpdate() } -func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { +func (m *ModelGithubWorkflow) handleEmptyWorkflows() { + m.selectedRepository.WorkflowName = "" + m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] No triggerable workflow found.", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) +} + +// ----------------------------------------------------------------------------- +// Branch Sync & Management +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflow) syncBranches(ctx context.Context) { defer m.skeleton.TriggerUpdate() m.status.Reset() - m.status.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching triggerable workflows...", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + m.status.SetProgressMessage(fmt.Sprintf("[%s] Fetching branches...", + m.selectedRepository.RepositoryName)) - // delete all rows - m.tableTriggerableWorkflow.SetRows([]table.Row{}) + branches, err := m.fetchBranches(ctx) + if err != nil { + return + } + + m.processBranches(branches) +} - triggerableWorkflows, err := m.github.GetTriggerableWorkflows(ctx, gu.GetTriggerableWorkflowsInput{ +func (m *ModelGithubWorkflow) fetchBranches(ctx context.Context) (*gu.GetRepositoryBranchesOutput, error) { + branches, err := m.github.GetRepositoryBranches(ctx, gu.GetRepositoryBranchesInput{ Repository: m.selectedRepository.RepositoryName, - Branch: m.selectedRepository.BranchName, }) - if errors.Is(err, context.Canceled) { - return - } else if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Triggerable workflows cannot be listed") - return + + if err != nil { + if !errors.Is(err, context.Canceled) { + m.status.SetError(err) + m.status.SetErrorMessage("Branches cannot be listed") + } + return nil, err } - if len(triggerableWorkflows.TriggerableWorkflows) == 0 { - m.selectedRepository.WorkflowName = "" - m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] No triggerable workflow found.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + return branches, nil +} + +func (m *ModelGithubWorkflow) processBranches(branches *gu.GetRepositoryBranchesOutput) { + if branches == nil || len(branches.Branches) == 0 { + m.handleEmptyBranches() return } - var tableRowsTriggerableWorkflow []table.Row - for _, workflow := range triggerableWorkflows.TriggerableWorkflows { - tableRowsTriggerableWorkflow = append(tableRowsTriggerableWorkflow, table.Row{ + branchNames := make([]string, len(branches.Branches)) + for i, branch := range branches.Branches { + branchNames[i] = branch.Name + } + + m.textInput.SetSuggestions(branchNames) + m.status.SetSuccessMessage(fmt.Sprintf("[%s] Branches fetched.", + m.selectedRepository.RepositoryName)) +} + +func (m *ModelGithubWorkflow) handleEmptyBranches() { + m.selectedRepository.BranchName = "" + m.status.SetDefaultMessage(fmt.Sprintf("[%s] No branches found.", + m.selectedRepository.RepositoryName)) +} + +// ----------------------------------------------------------------------------- +// Table Management +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflow) updateWorkflowTable(workflows []gu.TriggerableWorkflow) { + rows := make([]table.Row, 0, len(workflows)) + for _, workflow := range workflows { + rows = append(rows, table.Row{ workflow.Name, workflow.Path, }) } - sort.SliceStable(tableRowsTriggerableWorkflow, func(i, j int) bool { - return tableRowsTriggerableWorkflow[i][0] < tableRowsTriggerableWorkflow[j][0] + sort.SliceStable(rows, func(i, j int) bool { + return rows[i][0] < rows[j][0] }) - m.tableTriggerableWorkflow.SetRows(tableRowsTriggerableWorkflow) - - if len(tableRowsTriggerableWorkflow) > 0 { + m.tableTriggerableWorkflow.SetRows(rows) + if len(rows) > 0 { m.tableTriggerableWorkflow.SetCursor(0) } +} +func (m *ModelGithubWorkflow) finalizeUpdate() { m.tableReady = true - m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s] Triggerable workflows fetched.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s] Triggerable workflows fetched.", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) } -func (m *ModelGithubWorkflow) syncBranches(ctx context.Context) { - defer m.skeleton.TriggerUpdate() - - m.status.Reset() - m.status.SetProgressMessage(fmt.Sprintf("[%s] Fetching branches...", m.selectedRepository.RepositoryName)) - - branches, err := m.github.GetRepositoryBranches(ctx, gu.GetRepositoryBranchesInput{ - Repository: m.selectedRepository.RepositoryName, - }) - if errors.Is(err, context.Canceled) { - return - } else if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Branches cannot be listed") +func (m *ModelGithubWorkflow) handleTableInputs() { + if !m.tableReady { return } - if branches == nil || len(branches.Branches) == 0 { - m.selectedRepository.BranchName = "" - m.status.SetDefaultMessage(fmt.Sprintf("[%s] No branches found.", m.selectedRepository.RepositoryName)) - return + rows := m.tableTriggerableWorkflow.Rows() + selectedRow := m.tableTriggerableWorkflow.SelectedRow() + if len(rows) > 0 && len(selectedRow) > 0 { + m.selectedRepository.WorkflowName = selectedRow[1] } +} - var bs = make([]string, len(branches.Branches)) - for i, branch := range branches.Branches { - bs[i] = branch.Name - } +// ----------------------------------------------------------------------------- +// UI Rendering +// ----------------------------------------------------------------------------- - m.textInput.SetSuggestions(bs) +func (m *ModelGithubWorkflow) renderTable() string { + style := lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#3b698f")). + MarginLeft(1) - m.status.SetSuccessMessage(fmt.Sprintf("[%s] Branches fetched.", m.selectedRepository.RepositoryName)) + m.updateTableDimensions() + return style.Render(m.tableTriggerableWorkflow.View()) } -func (m *ModelGithubWorkflow) handleTableInputs(_ context.Context) { - if !m.tableReady { - return +func (m *ModelGithubWorkflow) renderBranchInput() string { + style := lipgloss.NewStyle(). + Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#3b698f")). + Padding(0, 1). + Width(m.skeleton.GetTerminalWidth() - 6). + MarginLeft(1) + + if len(m.textInput.AvailableSuggestions()) > 0 && m.textInput.Value() == "" { + m.textInput.Placeholder = fmt.Sprintf("Type to switch branch (default: %s)", m.mainBranch) } - // To avoid go routine leak - rows := m.tableTriggerableWorkflow.Rows() - selectedRow := m.tableTriggerableWorkflow.SelectedRow() + return style.Render(m.textInput.View()) +} - if len(rows) > 0 && len(selectedRow) > 0 { - m.selectedRepository.WorkflowName = selectedRow[1] +func (m *ModelGithubWorkflow) renderHelp() string { + helpStyle := WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + return helpStyle.Render(m.help.View(m.keys)) +} + +func (m *ModelGithubWorkflow) updateTableDimensions() { + termWidth := m.skeleton.GetTerminalWidth() + termHeight := m.skeleton.GetTerminalHeight() + + var tableWidth int + for _, t := range tableColumnsWorkflow { + tableWidth += t.Width + } + + newTableColumns := tableColumnsWorkflow + widthDiff := termWidth - tableWidth + if widthDiff > 0 { + newTableColumns[1].Width += widthDiff - 10 + m.tableTriggerableWorkflow.SetColumns(newTableColumns) + m.tableTriggerableWorkflow.SetHeight(termHeight - 17) } } diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index 38a930a..db54f63 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -4,8 +4,6 @@ import ( "context" "errors" "fmt" - "github.com/termkit/gama/internal/config" - "github.com/termkit/skeleton" "time" "github.com/charmbracelet/bubbles/help" @@ -13,63 +11,106 @@ import ( "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/termkit/gama/internal/config" gu "github.com/termkit/gama/internal/github/usecase" - "github.com/termkit/gama/internal/terminal/handler/status" - "github.com/termkit/gama/internal/terminal/handler/taboptions" - hdltypes "github.com/termkit/gama/internal/terminal/handler/types" "github.com/termkit/gama/pkg/browser" + "github.com/termkit/skeleton" ) +// ----------------------------------------------------------------------------- +// Model Definition +// ----------------------------------------------------------------------------- + type ModelGithubWorkflowHistory struct { + // Core dependencies skeleton *skeleton.Skeleton - // current handler's properties - tableReady bool - liveMode bool - liveModeInterval time.Duration - tableStyle lipgloss.Style - updateRound int - selectedWorkflowID int64 - lastRepository string - syncWorkflowHistoryContext context.Context - cancelSyncWorkflowHistory context.CancelFunc - workflows []gu.Workflow + github gu.UseCase - // shared properties - selectedRepository *hdltypes.SelectedRepository + // UI Components + Help help.Model + keys githubWorkflowHistoryKeyMap + tableWorkflowHistory table.Model + status *ModelStatus + modelTabOptions *ModelTabOptions - // use cases - github gu.UseCase + // Table state + tableReady bool + tableStyle lipgloss.Style + updateRound int + workflows []gu.Workflow + lastRepository string - // keymap - keys githubWorkflowHistoryKeyMap + // Live mode state + liveMode bool + liveModeInterval time.Duration - // models - Help help.Model - tableWorkflowHistory table.Model - status *status.ModelStatus + // Workflow state + selectedWorkflowID int64 + + // Context management + syncWorkflowHistoryContext context.Context + cancelSyncWorkflowHistory context.CancelFunc - modelTabOptions *taboptions.Options + // Shared state + selectedRepository *SelectedRepository } type workflowHistoryUpdateMsg struct { UpdateAfter time.Duration } +// ----------------------------------------------------------------------------- +// Constructor & Initialization +// ----------------------------------------------------------------------------- + func SetupModelGithubWorkflowHistory(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { cfg, err := config.LoadConfig() if err != nil { panic(fmt.Sprintf("failed to load config: %v", err)) } - var tableRowsWorkflowHistory []table.Row + m := &ModelGithubWorkflowHistory{ + // Initialize core dependencies + skeleton: sk, + github: githubUseCase, + + // Initialize UI components + Help: help.New(), + keys: githubWorkflowHistoryKeys, + status: SetupModelStatus(sk), + modelTabOptions: NewOptions(sk, SetupModelStatus(sk)), + + // Initialize state + selectedRepository: NewSelectedRepository(), + syncWorkflowHistoryContext: context.Background(), + cancelSyncWorkflowHistory: func() {}, + liveMode: cfg.Settings.LiveMode.Enabled, + liveModeInterval: cfg.Settings.LiveMode.Interval, + tableStyle: setupTableStyle(), + } + + // Setup table + m.tableWorkflowHistory = setupWorkflowHistoryTable() + + return m +} + +func setupTableStyle() lipgloss.Style { + return lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("#3b698f")). + MarginLeft(1) +} - tableWorkflowHistory := table.New( +func setupWorkflowHistoryTable() table.Model { + t := table.New( table.WithColumns(tableColumnsWorkflowHistory), - table.WithRows(tableRowsWorkflowHistory), + table.WithRows([]table.Row{}), table.WithFocused(true), table.WithHeight(7), ) + // Apply styles s := table.DefaultStyles() s.Header = s.Header. BorderStyle(lipgloss.NormalBorder()). @@ -80,89 +121,270 @@ func SetupModelGithubWorkflowHistory(sk *skeleton.Skeleton, githubUseCase gu.Use Foreground(lipgloss.Color("229")). Background(lipgloss.Color("57")). Bold(false) - tableWorkflowHistory.SetStyles(s) - - var tableStyle = lipgloss.NewStyle(). - BorderStyle(lipgloss.NormalBorder()). - BorderForeground(lipgloss.Color("#3b698f")).MarginLeft(1) - - modelStatus := status.SetupModelStatus(sk) - tabOptions := taboptions.NewOptions(sk, &modelStatus) - - return &ModelGithubWorkflowHistory{ - skeleton: sk, - liveMode: cfg.Settings.LiveMode.Enabled, - liveModeInterval: cfg.Settings.LiveMode.Interval, - Help: help.New(), - keys: githubWorkflowHistoryKeys, - github: githubUseCase, - tableWorkflowHistory: tableWorkflowHistory, - status: &modelStatus, - selectedRepository: hdltypes.NewSelectedRepository(), - modelTabOptions: tabOptions, - syncWorkflowHistoryContext: context.Background(), - cancelSyncWorkflowHistory: func() {}, - tableStyle: tableStyle, + t.SetStyles(s) + + t.KeyMap = table.KeyMap{ + LineUp: key.NewBinding(key.WithKeys("up"), key.WithHelp("↑", "up")), + LineDown: key.NewBinding(key.WithKeys("down"), key.WithHelp("↓", "down")), + PageUp: key.NewBinding(key.WithKeys("pgup"), key.WithHelp("pgup", "page up")), + PageDown: key.NewBinding(key.WithKeys("pgdown"), key.WithHelp("pgdn", "page down")), + GotoTop: key.NewBinding(key.WithKeys("home"), key.WithHelp("home", "go to start")), + GotoBottom: key.NewBinding(key.WithKeys("end"), key.WithHelp("end", "go to end")), } + + return t } +// ----------------------------------------------------------------------------- +// Bubbletea Model Implementation +// ----------------------------------------------------------------------------- + func (m *ModelGithubWorkflowHistory) Init() tea.Cmd { m.setupOptions() - m.ToggleLiveMode() + m.startLiveMode() return tea.Batch(m.modelTabOptions.Init()) } -func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - if m.lastRepository != m.selectedRepository.RepositoryName { - m.tableReady = false - m.cancelSyncWorkflowHistory() // cancel previous sync - m.lastRepository = m.selectedRepository.RepositoryName - - m.syncWorkflowHistoryContext, m.cancelSyncWorkflowHistory = context.WithCancel(context.Background()) - go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) +func (m *ModelGithubWorkflowHistory) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + // Handle repository changes + if cmd := m.handleRepositoryChange(); cmd != nil { + return m, cmd } - if m.workflows != nil { - m.selectedWorkflowID = m.workflows[m.tableWorkflowHistory.Cursor()].ID + cursor := m.tableWorkflowHistory.Cursor() + if m.workflows != nil && cursor >= 0 && cursor < len(m.workflows) { + m.selectedWorkflowID = m.workflows[cursor].ID } var cmds []tea.Cmd var cmd tea.Cmd + + // Handle different message types switch msg := msg.(type) { case tea.KeyMsg: - switch { - case key.Matches(msg, m.keys.Refresh): - go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) - case key.Matches(msg, m.keys.LiveMode): - m.liveMode = !m.liveMode + cmd = m.handleKeyMsg(msg) + if cmd != nil { + cmds = append(cmds, cmd) + } + case workflowHistoryUpdateMsg: + cmd = m.handleUpdateMsg(msg) + if cmd != nil { + cmds = append(cmds, cmd) + } + } + + // Update UI components + if cmd = m.updateUIComponents(msg); cmd != nil { + cmds = append(cmds, cmd) + } + + return m, tea.Batch(cmds...) +} + +func (m *ModelGithubWorkflowHistory) View() string { + return lipgloss.JoinVertical(lipgloss.Top, + m.renderTable(), + m.modelTabOptions.View(), + m.status.View(), + m.renderHelp(), + ) +} + +// ----------------------------------------------------------------------------- +// Event Handlers +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflowHistory) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { + switch { + case key.Matches(msg, m.keys.Refresh): + go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) + return nil + case key.Matches(msg, m.keys.LiveMode): + return m.toggleLiveMode() + } + return nil +} + +func (m *ModelGithubWorkflowHistory) handleUpdateMsg(msg workflowHistoryUpdateMsg) tea.Cmd { + go func() { + time.Sleep(msg.UpdateAfter) + m.syncWorkflowHistory(m.syncWorkflowHistoryContext) + m.skeleton.TriggerUpdate() + }() + return nil +} + +// ----------------------------------------------------------------------------- +// Live Mode Management +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflowHistory) startLiveMode() { + go func() { + for range time.NewTicker(m.liveModeInterval).C { if m.liveMode { - m.status.SetSuccessMessage("Live mode enabled") - m.skeleton.UpdateWidgetValue("live", "Live Mode: On") - } else { - m.status.SetSuccessMessage("Live mode disabled") - m.skeleton.UpdateWidgetValue("live", "Live Mode: Off") + m.skeleton.TriggerUpdateWithMsg(workflowHistoryUpdateMsg{UpdateAfter: time.Nanosecond}) } } - case workflowHistoryUpdateMsg: - go func() { - time.Sleep(msg.UpdateAfter) - m.syncWorkflowHistory(m.syncWorkflowHistoryContext) - m.skeleton.TriggerUpdate() - }() + }() +} + +func (m *ModelGithubWorkflowHistory) toggleLiveMode() tea.Cmd { + m.liveMode = !m.liveMode + if m.liveMode { + m.status.SetSuccessMessage("Live mode enabled") + m.skeleton.UpdateWidgetValue("live", "Live Mode: On") + } else { + m.status.SetSuccessMessage("Live mode disabled") + m.skeleton.UpdateWidgetValue("live", "Live Mode: Off") } + return nil +} - m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) - cmds = append(cmds, cmd) +// ----------------------------------------------------------------------------- +// Repository Change Handling +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflowHistory) handleRepositoryChange() tea.Cmd { + if m.lastRepository == m.selectedRepository.RepositoryName { + return nil + } + if m.cancelSyncWorkflowHistory != nil { + m.cancelSyncWorkflowHistory() + } + + m.lastRepository = m.selectedRepository.RepositoryName + m.syncWorkflowHistoryContext, m.cancelSyncWorkflowHistory = context.WithCancel(context.Background()) + + go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) + return nil +} + +// ----------------------------------------------------------------------------- +// Workflow History Sync +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { + defer m.skeleton.TriggerUpdate() + + m.initializeSyncState() + workflowHistory, err := m.fetchWorkflowHistory(ctx) + if err != nil { + return + } + + m.processWorkflowHistory(workflowHistory) +} + +func (m *ModelGithubWorkflowHistory) initializeSyncState() { + m.tableReady = false + m.status.Reset() + m.status.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching workflow history...", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + m.modelTabOptions.SetStatus(StatusWait) + m.clearWorkflowHistory() +} + +func (m *ModelGithubWorkflowHistory) clearWorkflowHistory() { + m.tableWorkflowHistory.SetRows([]table.Row{}) + m.workflows = nil +} + +func (m *ModelGithubWorkflowHistory) fetchWorkflowHistory(ctx context.Context) (*gu.GetWorkflowHistoryOutput, error) { + history, err := m.github.GetWorkflowHistory(ctx, gu.GetWorkflowHistoryInput{ + Repository: m.selectedRepository.RepositoryName, + Branch: m.selectedRepository.BranchName, + }) + + if err != nil { + if !errors.Is(err, context.Canceled) { + m.status.SetError(err) + m.status.SetErrorMessage("Workflow history cannot be listed") + } + return nil, err + } + + return history, nil +} + +// ----------------------------------------------------------------------------- +// Workflow History Processing +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflowHistory) processWorkflowHistory(history *gu.GetWorkflowHistoryOutput) { + if len(history.Workflows) == 0 { + m.handleEmptyWorkflowHistory() + return + } + + m.workflows = history.Workflows + m.updateWorkflowTable() + m.finalizeUpdate() +} + +func (m *ModelGithubWorkflowHistory) handleEmptyWorkflowHistory() { + m.modelTabOptions.SetStatus(StatusNone) + m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] No workflow history found.", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) +} + +func (m *ModelGithubWorkflowHistory) updateWorkflowTable() { + rows := make([]table.Row, 0, len(m.workflows)) + for _, workflow := range m.workflows { + rows = append(rows, table.Row{ + workflow.WorkflowName, + workflow.ActionName, + workflow.TriggeredBy, + workflow.StartedAt, + workflow.Status, + workflow.Duration, + }) + } + m.tableWorkflowHistory.SetRows(rows) +} + +func (m *ModelGithubWorkflowHistory) finalizeUpdate() { + m.tableReady = true + m.tableWorkflowHistory.SetCursor(0) + m.modelTabOptions.SetStatus(StatusIdle) + m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow history fetched.", + m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) +} + +// ----------------------------------------------------------------------------- +// UI Component Updates +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflowHistory) updateUIComponents(msg tea.Msg) tea.Cmd { + var cmds []tea.Cmd + var cmd tea.Cmd + + // Update table and handle navigation m.tableWorkflowHistory, cmd = m.tableWorkflowHistory.Update(msg) cmds = append(cmds, cmd) - return m, tea.Batch(cmds...) + // Update tab options + m.modelTabOptions, cmd = m.modelTabOptions.Update(msg) + cmds = append(cmds, cmd) + + return tea.Batch(cmds...) } -func (m *ModelGithubWorkflowHistory) View() string { - helpWindowStyle := hdltypes.WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) +// ----------------------------------------------------------------------------- +// UI Rendering +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflowHistory) renderTable() string { + m.updateTableDimensions() + return m.tableStyle.Render(m.tableWorkflowHistory.View()) +} +func (m *ModelGithubWorkflowHistory) renderHelp() string { + helpStyle := WindowStyleHelp.Width(m.skeleton.GetTerminalWidth() - 4) + return helpStyle.Render(m.ViewHelp()) +} + +func (m *ModelGithubWorkflowHistory) updateTableDimensions() { termWidth := m.skeleton.GetTerminalWidth() termHeight := m.skeleton.GetTerminalHeight() @@ -185,149 +407,81 @@ func (m *ModelGithubWorkflowHistory) View() string { } m.tableWorkflowHistory.SetHeight(termHeight - 17) - - return lipgloss.JoinVertical(lipgloss.Top, - m.tableStyle.Render(m.tableWorkflowHistory.View()), m.modelTabOptions.View(), - m.status.View(), helpWindowStyle.Render(m.ViewHelp())) } -func (m *ModelGithubWorkflowHistory) setupOptions() { - openInBrowser := func() { - m.status.SetProgressMessage("Opening in browser...") - - var selectedWorkflow = fmt.Sprintf("https://github.com/%s/actions/runs/%d", m.selectedRepository.RepositoryName, m.selectedWorkflowID) +// ----------------------------------------------------------------------------- +// Option Management +// ----------------------------------------------------------------------------- - err := browser.OpenInBrowser(selectedWorkflow) - if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Failed to open in browser") - return - } - m.status.SetSuccessMessage("Opened in browser") - } +func (m *ModelGithubWorkflowHistory) setupOptions() { + m.modelTabOptions.AddOption("Open in browser", m.openInBrowser) + m.modelTabOptions.AddOption("Rerun failed jobs", m.rerunFailedJobs) + m.modelTabOptions.AddOption("Rerun workflow", m.rerunWorkflow) + m.modelTabOptions.AddOption("Cancel workflow", m.cancelWorkflow) +} - reRunFailedJobs := func() { - m.status.SetProgressMessage("Re-running failed jobs...") +func (m *ModelGithubWorkflowHistory) openInBrowser() { + m.status.SetProgressMessage("Opening in browser...") - _, err := m.github.ReRunFailedJobs(context.Background(), gu.ReRunFailedJobsInput{ - Repository: m.selectedRepository.RepositoryName, - WorkflowID: m.selectedWorkflowID, - }) + url := fmt.Sprintf("https://github.com/%s/actions/runs/%d", + m.selectedRepository.RepositoryName, + m.selectedWorkflowID) - if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Failed to re-run failed jobs") - return - } - - m.status.SetSuccessMessage("Re-ran failed jobs") + if err := browser.OpenInBrowser(url); err != nil { + m.status.SetError(err) + m.status.SetErrorMessage("Failed to open in browser") + return } + m.status.SetSuccessMessage("Opened in browser") +} - reRunWorkflow := func() { - m.status.SetProgressMessage("Re-running workflow...") - - _, err := m.github.ReRunWorkflow(context.Background(), gu.ReRunWorkflowInput{ - Repository: m.selectedRepository.RepositoryName, - WorkflowID: m.selectedWorkflowID, - }) +func (m *ModelGithubWorkflowHistory) rerunFailedJobs() { + m.status.SetProgressMessage("Re-running failed jobs...") - if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Failed to re-run workflow") - return - } + _, err := m.github.ReRunFailedJobs(context.Background(), gu.ReRunFailedJobsInput{ + Repository: m.selectedRepository.RepositoryName, + WorkflowID: m.selectedWorkflowID, + }) - m.status.SetSuccessMessage("Re-ran workflow") + if err != nil { + m.status.SetError(err) + m.status.SetErrorMessage("Failed to re-run failed jobs") + return } - cancelWorkflow := func() { - m.status.SetProgressMessage("Canceling workflow...") + m.status.SetSuccessMessage("Re-ran failed jobs") +} - _, err := m.github.CancelWorkflow(context.Background(), gu.CancelWorkflowInput{ - Repository: m.selectedRepository.RepositoryName, - WorkflowID: m.selectedWorkflowID, - }) +func (m *ModelGithubWorkflowHistory) rerunWorkflow() { + m.status.SetProgressMessage("Re-running workflow...") - if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Failed to cancel workflow") - return - } + _, err := m.github.ReRunWorkflow(context.Background(), gu.ReRunWorkflowInput{ + Repository: m.selectedRepository.RepositoryName, + WorkflowID: m.selectedWorkflowID, + }) - m.status.SetSuccessMessage("Canceled workflow") + if err != nil { + m.status.SetError(err) + m.status.SetErrorMessage("Failed to re-run workflow") + return } - 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) ToggleLiveMode() { - // send UpdateWorkflowHistoryMsg to update the workflow history every 5 seconds with ticker - // send only if liveMode is true - go func() { - t := time.NewTicker(m.liveModeInterval) - for { - select { - case <-t.C: - if m.liveMode { - m.skeleton.TriggerUpdateWithMsg(workflowHistoryUpdateMsg{UpdateAfter: time.Nanosecond}) - } - } - } - }() + m.status.SetSuccessMessage("Re-ran workflow") } -func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { - defer m.skeleton.TriggerUpdate() - - m.tableReady = false - m.status.Reset() - m.status.SetProgressMessage(fmt.Sprintf("[%s@%s] Fetching workflow history...", - m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) - m.modelTabOptions.SetStatus(taboptions.OptionWait) - - // delete all rows - m.tableWorkflowHistory.SetRows([]table.Row{}) - - // delete old workflows - m.workflows = nil +func (m *ModelGithubWorkflowHistory) cancelWorkflow() { + m.status.SetProgressMessage("Canceling workflow...") - workflowHistory, err := m.github.GetWorkflowHistory(ctx, gu.GetWorkflowHistoryInput{ + _, err := m.github.CancelWorkflow(context.Background(), gu.CancelWorkflowInput{ Repository: m.selectedRepository.RepositoryName, - Branch: m.selectedRepository.BranchName, + WorkflowID: m.selectedWorkflowID, }) - if errors.Is(err, context.Canceled) { - return - } else if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Workflow history cannot be listed") - return - } - if len(workflowHistory.Workflows) == 0 { - m.modelTabOptions.SetStatus(taboptions.OptionNone) - m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] No workflow history found.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + if err != nil { + m.status.SetError(err) + m.status.SetErrorMessage("Failed to cancel workflow") return } - m.workflows = workflowHistory.Workflows - - var tableRowsWorkflowHistory []table.Row - for _, workflowRun := range m.workflows { - tableRowsWorkflowHistory = append(tableRowsWorkflowHistory, table.Row{ - workflowRun.WorkflowName, - workflowRun.ActionName, - workflowRun.TriggeredBy, - workflowRun.StartedAt, - workflowRun.Status, - workflowRun.Duration, - }) - } - - m.tableReady = true - m.tableWorkflowHistory.SetRows(tableRowsWorkflowHistory) - m.tableWorkflowHistory.SetCursor(0) - m.modelTabOptions.SetStatus(taboptions.OptionIdle) - m.status.SetSuccessMessage(fmt.Sprintf("[%s@%s] Workflow history fetched.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + m.status.SetSuccessMessage("Canceled workflow") } diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index 8a90e89..a058817 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -5,7 +5,6 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/termkit/gama/internal/config" gu "github.com/termkit/gama/internal/github/usecase" - hdltypes "github.com/termkit/gama/internal/terminal/handler/types" pkgversion "github.com/termkit/gama/pkg/version" "github.com/termkit/skeleton" ) @@ -32,8 +31,8 @@ func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Mod s.AddWidget("live", "Live Mode: Off") } - s.SetTerminalViewportWidth(hdltypes.MinTerminalWidth) - s.SetTerminalViewportHeight(hdltypes.MinTerminalHeight) + s.SetTerminalViewportWidth(MinTerminalWidth) + s.SetTerminalViewportHeight(MinTerminalHeight) s.KeyMap.SetKeyNextTab(handlerKeys.SwitchTabRight) s.KeyMap.SetKeyPrevTab(handlerKeys.SwitchTabLeft) diff --git a/internal/terminal/handler/keymap.go b/internal/terminal/handler/keymap.go index 6b832f3..1feb45c 100644 --- a/internal/terminal/handler/keymap.go +++ b/internal/terminal/handler/keymap.go @@ -2,6 +2,7 @@ package handler import ( "fmt" + "github.com/termkit/gama/internal/config" teakey "github.com/charmbracelet/bubbles/key" diff --git a/internal/terminal/handler/status/status.go b/internal/terminal/handler/status.go similarity index 86% rename from internal/terminal/handler/status/status.go rename to internal/terminal/handler/status.go index 0cc081a..05897f6 100644 --- a/internal/terminal/handler/status/status.go +++ b/internal/terminal/handler/status.go @@ -1,11 +1,11 @@ -package status +package handler import ( "fmt" + "strings" + "github.com/charmbracelet/lipgloss" - ts "github.com/termkit/gama/internal/terminal/handler/types" "github.com/termkit/skeleton" - "strings" ) type ModelStatus struct { @@ -36,8 +36,8 @@ const ( MessageTypeSuccess MessageType = "success" ) -func SetupModelStatus(skeleton *skeleton.Skeleton) ModelStatus { - return ModelStatus{ +func SetupModelStatus(skeleton *skeleton.Skeleton) *ModelStatus { + return &ModelStatus{ skeleton: skeleton, err: nil, errorMessage: "", @@ -50,20 +50,20 @@ func (m *ModelStatus) View() string { doc := strings.Builder{} if m.HaveError() { - windowStyle = ts.WindowStyleError.Width(width) + windowStyle = WindowStyleError.Width(width) doc.WriteString(windowStyle.Render(m.viewError())) return lipgloss.JoinHorizontal(lipgloss.Top, doc.String()) } switch m.messageType { case MessageTypeDefault: - windowStyle = ts.WindowStyleDefault.Width(width) + windowStyle = WindowStyleDefault.Width(width) case MessageTypeProgress: - windowStyle = ts.WindowStyleProgress.Width(width) + windowStyle = WindowStyleProgress.Width(width) case MessageTypeSuccess: - windowStyle = ts.WindowStyleSuccess.Width(width) + windowStyle = WindowStyleSuccess.Width(width) default: - windowStyle = ts.WindowStyleDefault.Width(width) + windowStyle = WindowStyleDefault.Width(width) } doc.WriteString(windowStyle.Render(m.viewMessage())) diff --git a/internal/terminal/handler/taboptions/taboptions.go b/internal/terminal/handler/taboptions.go similarity index 72% rename from internal/terminal/handler/taboptions/taboptions.go rename to internal/terminal/handler/taboptions.go index 2a9e940..f7287b8 100644 --- a/internal/terminal/handler/taboptions/taboptions.go +++ b/internal/terminal/handler/taboptions.go @@ -1,23 +1,23 @@ -package taboptions +package handler import ( "fmt" - "github.com/termkit/skeleton" "strings" "time" + "github.com/termkit/skeleton" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/termkit/gama/internal/terminal/handler/status" ) -type Options struct { +type ModelTabOptions struct { skeleton *skeleton.Skeleton Style lipgloss.Style - status *status.ModelStatus - previousStatus status.ModelStatus + status *ModelStatus + previousStatus ModelStatus modelLock bool optionStatus OptionStatus @@ -37,21 +37,21 @@ type Options struct { type OptionStatus string const ( - // OptionIdle is for when the options are ready to use - OptionIdle OptionStatus = "Idle" + // StatusIdle is for when the options are ready to use + StatusIdle OptionStatus = "Idle" - // OptionWait is for when the options are not ready to use - OptionWait OptionStatus = "Wait" + // StatusWait is for when the options are not ready to use + StatusWait OptionStatus = "Wait" - // OptionNone is for when the options are not usable - OptionNone OptionStatus = "None" + // StatusNone is for when the options are not usable + StatusNone OptionStatus = "None" ) func (o OptionStatus) String() string { return string(o) } -func NewOptions(sk *skeleton.Skeleton, modelStatus *status.ModelStatus) *Options { +func NewOptions(sk *skeleton.Skeleton, modelStatus *ModelStatus) *ModelTabOptions { var b = lipgloss.RoundedBorder() b.Right = "├" b.Left = "┤" @@ -62,34 +62,34 @@ func NewOptions(sk *skeleton.Skeleton, modelStatus *status.ModelStatus) *Options Border(b) var initialOptions = []string{ - OptionWait.String(), + StatusWait.String(), } var initialOptionsAction = []string{ - OptionWait.String(), + StatusWait.String(), } optionsWithFunc := make(map[int]func()) optionsWithFunc[0] = func() {} // NO OPERATION - return &Options{ + return &ModelTabOptions{ skeleton: sk, Style: OptionsStyle, options: initialOptions, optionsAction: initialOptionsAction, optionsWithFunc: optionsWithFunc, - optionStatus: OptionWait, + optionStatus: StatusWait, status: modelStatus, } } -func (o *Options) Init() tea.Cmd { +func (o *ModelTabOptions) Init() tea.Cmd { return nil } -func (o *Options) Update(msg tea.Msg) (*Options, tea.Cmd) { +func (o *ModelTabOptions) Update(msg tea.Msg) (*ModelTabOptions, tea.Cmd) { var cmd tea.Cmd - if o.optionStatus == OptionWait || o.optionStatus == OptionNone { + if o.optionStatus == StatusWait || o.optionStatus == StatusNone { return o, cmd } @@ -112,7 +112,7 @@ func (o *Options) Update(msg tea.Msg) (*Options, tea.Cmd) { return o, cmd } -func (o *Options) View() string { +func (o *ModelTabOptions) View() string { var style = o.Style.Foreground(lipgloss.Color("15")) var opts []string @@ -120,9 +120,9 @@ func (o *Options) View() string { for i, option := range o.optionsAction { switch o.optionStatus { - case OptionWait: + case StatusWait: style = style.BorderForeground(lipgloss.Color("208")) - case OptionNone: + case StatusNone: style = style.BorderForeground(lipgloss.Color("240")) default: isActive := i == o.cursor @@ -139,7 +139,7 @@ func (o *Options) View() string { return lipgloss.JoinHorizontal(lipgloss.Top, opts...) } -func (o *Options) resetOptionsWithOriginal() { +func (o *ModelTabOptions) resetOptionsWithOriginal() { if o.isTabSelected { return } @@ -153,12 +153,12 @@ func (o *Options) resetOptionsWithOriginal() { } o.modelLock = false o.switchToPreviousError() - o.optionsAction[0] = string(OptionIdle) + o.optionsAction[0] = string(StatusIdle) o.cursor = 0 o.isTabSelected = false } -func (o *Options) updateCursor(cursor int) { +func (o *ModelTabOptions) updateCursor(cursor int) { if cursor < len(o.options) { o.cursor = cursor o.showAreYouSure() @@ -166,13 +166,13 @@ func (o *Options) updateCursor(cursor int) { } } -func (o *Options) SetStatus(status OptionStatus) { +func (o *ModelTabOptions) SetStatus(status OptionStatus) { o.optionStatus = status o.options[0] = status.String() o.optionsAction[0] = status.String() } -func (o *Options) AddOption(option string, action func()) { +func (o *ModelTabOptions) AddOption(option string, action func()) { var optionWithNumber string var optionNumber = len(o.options) optionWithNumber = fmt.Sprintf("%d) %s", optionNumber, option) @@ -181,13 +181,13 @@ func (o *Options) AddOption(option string, action func()) { o.optionsWithFunc[optionNumber] = action } -func (o *Options) getOptionMessage() string { +func (o *ModelTabOptions) getOptionMessage() string { option := o.options[o.cursor] option = strings.TrimPrefix(option, fmt.Sprintf("%d) ", o.cursor)) return option } -func (o *Options) showAreYouSure() { +func (o *ModelTabOptions) showAreYouSure() { var yellowStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("4")).Blink(true) if !o.modelLock { @@ -203,14 +203,14 @@ func (o *Options) showAreYouSure() { } -func (o *Options) switchToPreviousError() { +func (o *ModelTabOptions) switchToPreviousError() { if o.modelLock { return } *o.status = o.previousStatus } -func (o *Options) executeOption() { +func (o *ModelTabOptions) executeOption() { go o.optionsWithFunc[o.cursor]() o.cursor = 0 o.timer = -1 diff --git a/internal/terminal/handler/types.go b/internal/terminal/handler/types.go index abeebd1..0424d92 100644 --- a/internal/terminal/handler/types.go +++ b/internal/terminal/handler/types.go @@ -1 +1,56 @@ package handler + +import ( + "sync" + + "github.com/charmbracelet/lipgloss" +) + +// Types +type SelectedRepository struct { + RepositoryName string + WorkflowName string + BranchName string +} + +// Constants +const ( + MinTerminalWidth = 102 + MinTerminalHeight = 24 + + DefaultTableHeight = 13 + DefaultTerminalWidth = 80 + DefaultTerminalHeight = 24 + + ColorPrimary = "#3b698f" + ColorSecondary = "#ff0055" + ColorError = "#ff0000" +) + +// Styles +var ( + WindowStyleOrange = lipgloss.NewStyle().BorderForeground(lipgloss.Color("#ffaf00")).Border(lipgloss.RoundedBorder()) + WindowStyleRed = lipgloss.NewStyle().BorderForeground(lipgloss.Color("9")).Border(lipgloss.RoundedBorder()) + WindowStyleGreen = lipgloss.NewStyle().BorderForeground(lipgloss.Color("10")).Border(lipgloss.RoundedBorder()) + WindowStyleGray = lipgloss.NewStyle().BorderForeground(lipgloss.Color("240")).Border(lipgloss.RoundedBorder()) + WindowStyleWhite = lipgloss.NewStyle().BorderForeground(lipgloss.Color("255")).Border(lipgloss.RoundedBorder()) + + WindowStyleHelp = WindowStyleGray.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) + WindowStyleError = WindowStyleRed.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) + WindowStyleProgress = WindowStyleOrange.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) + WindowStyleSuccess = WindowStyleGreen.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) + WindowStyleDefault = WindowStyleWhite.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) +) + +// Constructor +var ( + onceSelectedRepository sync.Once + selectedRepository *SelectedRepository +) + +func NewSelectedRepository() *SelectedRepository { + onceSelectedRepository.Do(func() { + selectedRepository = &SelectedRepository{} + }) + return selectedRepository +} diff --git a/internal/terminal/handler/types/style.go b/internal/terminal/handler/types/style.go deleted file mode 100644 index 72f4347..0000000 --- a/internal/terminal/handler/types/style.go +++ /dev/null @@ -1,19 +0,0 @@ -package types - -import ( - "github.com/charmbracelet/lipgloss" -) - -var ( - WindowStyleOrange = lipgloss.NewStyle().BorderForeground(lipgloss.Color("#ffaf00")).Border(lipgloss.RoundedBorder()) - WindowStyleRed = lipgloss.NewStyle().BorderForeground(lipgloss.Color("9")).Border(lipgloss.RoundedBorder()) - WindowStyleGreen = lipgloss.NewStyle().BorderForeground(lipgloss.Color("10")).Border(lipgloss.RoundedBorder()) - WindowStyleGray = lipgloss.NewStyle().BorderForeground(lipgloss.Color("240")).Border(lipgloss.RoundedBorder()) - WindowStyleWhite = lipgloss.NewStyle().BorderForeground(lipgloss.Color("255")).Border(lipgloss.RoundedBorder()) - - WindowStyleHelp = WindowStyleGray.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) - WindowStyleError = WindowStyleRed.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) - WindowStyleProgress = WindowStyleOrange.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) - WindowStyleSuccess = WindowStyleGreen.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) - WindowStyleDefault = WindowStyleWhite.Margin(0, 0, 0, 0).Padding(0, 2, 0, 2) -) diff --git a/internal/terminal/handler/types/types.go b/internal/terminal/handler/types/types.go deleted file mode 100644 index b4df73f..0000000 --- a/internal/terminal/handler/types/types.go +++ /dev/null @@ -1,30 +0,0 @@ -package types - -import ( - "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 -} - -// ---------------------------------------------- - -const ( - MinTerminalWidth = 102 - MinTerminalHeight = 24 -) From f5f501e47658f8d20009f9afaf8cddaab4962761 Mon Sep 17 00:00:00 2001 From: Engin Date: Tue, 7 Jan 2025 00:50:04 +0300 Subject: [PATCH 44/51] Update go.mod and go.sum to include new dependencies and upgrade existing ones --- go.mod | 6 +++--- go.sum | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dd3874a..8b8f3c1 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 github.com/termkit/skeleton v0.2.0 + golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 gopkg.in/yaml.v3 v3.0.1 ) @@ -38,13 +39,12 @@ require ( github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/cast v1.7.1 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e // indirect golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) diff --git a/go.sum b/go.sum index a971264..a2f5c25 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= @@ -88,12 +90,16 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e h1:4qufH0hlUYs6AO6XmZC3GqfDPGSXHVXUFR6OND+iJX4= golang.org/x/exp v0.0.0-20241215155358-4a5509556b9e/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588= +golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From b57e81e3ba9af392de3dc0647c660d6bd0277ab2 Mon Sep 17 00:00:00 2001 From: Engin Date: Tue, 7 Jan 2025 00:51:17 +0300 Subject: [PATCH 45/51] Enhance GitHub workflow handling and state management - Filter triggerable workflows to only include those from the ".github/workflows/" directory. - Introduce lastBranch tracking in ModelGithubTrigger for improved workflow synchronization. - Refactor ModelGithubWorkflow to manage repository and branch states more effectively. - Implement state management for available workflows, updating UI components based on workflow presence. - Improve user feedback with updated placeholders and blur effects when no workflows are available. These changes improve the usability and responsiveness of the terminal handler components. --- internal/github/repository/repository.go | 8 +- internal/terminal/handler/ghtrigger.go | 7 +- internal/terminal/handler/ghworkflow.go | 135 +++++++++++++++++++---- 3 files changed, 124 insertions(+), 26 deletions(-) diff --git a/internal/github/repository/repository.go b/internal/github/repository/repository.go index 454bd32..2a291a0 100644 --- a/internal/github/repository/repository.go +++ b/internal/github/repository/repository.go @@ -190,7 +190,13 @@ func (r *Repo) GetTriggerableWorkflows(ctx context.Context, repository string) ( // Filter workflows to only include those that are dispatchable and manually triggerable for _, workflow := range workflows.Workflows { - go r.workerGetTriggerableWorkflows(ctx, repository, workflow, results, errs) + // if workflow.Path starts with .github/workflows/ + if strings.HasPrefix(workflow.Path, ".github/workflows/") { + go r.workerGetTriggerableWorkflows(ctx, repository, workflow, results, errs) + } else { + // do not send but we created a channel for it + results <- nil + } } // Collect the results and errors diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index e4abdf0..672a86c 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -58,6 +58,9 @@ type ModelGithubTrigger struct { // Shared state selectedRepository *SelectedRepository + + // Track last branch for refresh + lastBranch string } // ----------------------------------------------------------------------------- @@ -170,6 +173,7 @@ func (m *ModelGithubTrigger) handleWorkflowChange() tea.Cmd { } if m.shouldSyncWorkflow() { + m.lastBranch = m.selectedRepository.BranchName return m.initializeWorkflowSync() } @@ -185,7 +189,8 @@ func (m *ModelGithubTrigger) handleNoWorkflow() { func (m *ModelGithubTrigger) shouldSyncWorkflow() bool { return m.selectedRepository.WorkflowName != "" && (m.selectedRepository.WorkflowName != m.selectedWorkflow || - m.selectedRepository.RepositoryName != m.selectedRepositoryName) + m.selectedRepository.RepositoryName != m.selectedRepositoryName || + m.lastBranch != m.selectedRepository.BranchName) } func (m *ModelGithubTrigger) initializeWorkflowSync() tea.Cmd { diff --git a/internal/terminal/handler/ghworkflow.go b/internal/terminal/handler/ghworkflow.go index 621a6cb..95db5ff 100644 --- a/internal/terminal/handler/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow.go @@ -33,9 +33,7 @@ type ModelGithubWorkflow struct { textInput textinput.Model // Table state - tableReady bool - lastRepository string - mainBranch string + tableReady bool // Context management syncTriggerableWorkflowsContext context.Context @@ -43,6 +41,22 @@ type ModelGithubWorkflow struct { // Shared state selectedRepository *SelectedRepository + + // Indicates if there are any available workflows + hasWorkflows bool + lastSelectedRepository string // Track last repository for state persistence + + // State management + state struct { + Ready bool + Repository struct { + Current string + Last string + Branch string + HasFlows bool + } + Syncing bool + } } // ----------------------------------------------------------------------------- @@ -67,8 +81,10 @@ func SetupModelGithubWorkflow(sk *skeleton.Skeleton, githubUseCase gu.UseCase) * cancelSyncTriggerableWorkflows: func() {}, } - // Setup table + // Setup table and blur initially m.tableTriggerableWorkflow = setupWorkflowTable() + m.tableTriggerableWorkflow.Blur() + m.textInput.Blur() return m } @@ -121,10 +137,18 @@ func setupWorkflowTable() table.Model { // ----------------------------------------------------------------------------- func (m *ModelGithubWorkflow) Init() tea.Cmd { + // Check initial state + if m.lastSelectedRepository == m.selectedRepository.RepositoryName && !m.hasWorkflows { + m.skeleton.LockTab("trigger") + // Blur components initially + m.tableTriggerableWorkflow.Blur() + m.textInput.Blur() + } return nil } func (m *ModelGithubWorkflow) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + // Check repository change and return command if exists if cmd := m.handleRepositoryChange(); cmd != nil { return m, cmd } @@ -159,17 +183,13 @@ func (m *ModelGithubWorkflow) View() string { // ----------------------------------------------------------------------------- func (m *ModelGithubWorkflow) handleRepositoryChange() tea.Cmd { - if m.lastRepository != m.selectedRepository.RepositoryName { - m.tableReady = false - m.cancelSyncTriggerableWorkflows() - - m.lastRepository = m.selectedRepository.RepositoryName - m.mainBranch = m.selectedRepository.BranchName - - m.syncTriggerableWorkflowsContext, m.cancelSyncTriggerableWorkflows = context.WithCancel(context.Background()) - - go m.syncTriggerableWorkflows(m.syncTriggerableWorkflowsContext) - go m.syncBranches(m.syncTriggerableWorkflowsContext) + if m.state.Repository.Current != m.selectedRepository.RepositoryName { + m.state.Ready = false + m.state.Repository.Current = m.selectedRepository.RepositoryName + m.state.Repository.Branch = m.selectedRepository.BranchName + m.syncWorkflows() + } else if !m.state.Repository.HasFlows { + m.skeleton.LockTab("trigger") } return nil } @@ -180,19 +200,20 @@ func (m *ModelGithubWorkflow) handleRepositoryChange() tea.Cmd { func (m *ModelGithubWorkflow) handleBranchSelection() { selectedBranch := m.textInput.Value() - if selectedBranch == "" { - m.selectedRepository.BranchName = m.mainBranch - m.skeleton.UnlockTabs() - return - } - if m.isBranchValid(selectedBranch) { + // Set branch + if selectedBranch == "" { + m.selectedRepository.BranchName = m.state.Repository.Branch + } else if m.isBranchValid(selectedBranch) { m.selectedRepository.BranchName = selectedBranch - m.skeleton.UnlockTabs() } else { m.status.SetErrorMessage(fmt.Sprintf("Branch %s does not exist", selectedBranch)) m.skeleton.LockTabsToTheRight() + return } + + // Update tab state + m.updateTabState() } func (m *ModelGithubWorkflow) isBranchValid(branch string) bool { @@ -208,6 +229,26 @@ func (m *ModelGithubWorkflow) isBranchValid(branch string) bool { // Workflow Sync & Management // ----------------------------------------------------------------------------- +func (m *ModelGithubWorkflow) syncWorkflows() { + if m.state.Syncing { + m.cancelSyncTriggerableWorkflows() + } + + ctx, cancel := context.WithCancel(context.Background()) + m.cancelSyncTriggerableWorkflows = cancel + m.state.Syncing = true + + go func() { + defer func() { + m.state.Syncing = false + m.skeleton.TriggerUpdate() + }() + + m.syncBranches(ctx) + m.syncTriggerableWorkflows(ctx) + }() +} + func (m *ModelGithubWorkflow) syncTriggerableWorkflows(ctx context.Context) { defer m.skeleton.TriggerUpdate() @@ -245,19 +286,49 @@ func (m *ModelGithubWorkflow) fetchTriggerableWorkflows(ctx context.Context) (*g } func (m *ModelGithubWorkflow) processWorkflows(workflows *gu.GetTriggerableWorkflowsOutput) { - if len(workflows.TriggerableWorkflows) == 0 { + m.state.Repository.HasFlows = len(workflows.TriggerableWorkflows) > 0 + m.state.Repository.Current = m.selectedRepository.RepositoryName + m.state.Ready = true + + if !m.state.Repository.HasFlows { m.handleEmptyWorkflows() return } m.updateWorkflowTable(workflows.TriggerableWorkflows) + m.updateTabState() m.finalizeUpdate() + + // Focus components when workflows exist + m.tableTriggerableWorkflow.Focus() + m.textInput.Focus() } func (m *ModelGithubWorkflow) handleEmptyWorkflows() { m.selectedRepository.WorkflowName = "" + m.skeleton.LockTab("trigger") + + // Blur components when no workflows + m.tableTriggerableWorkflow.Blur() + m.textInput.Blur() + m.status.SetDefaultMessage(fmt.Sprintf("[%s@%s] No triggerable workflow found.", m.selectedRepository.RepositoryName, m.selectedRepository.BranchName)) + + m.fillTableWithEmptyMessage() +} + +func (m *ModelGithubWorkflow) fillTableWithEmptyMessage() { + var rows []table.Row + for i := 0; i < 100; i++ { + rows = append(rows, table.Row{ + "EMPTY", + "No triggerable workflow found", + }) + } + + m.tableTriggerableWorkflow.SetRows(rows) + m.tableTriggerableWorkflow.SetCursor(0) } // ----------------------------------------------------------------------------- @@ -381,7 +452,11 @@ func (m *ModelGithubWorkflow) renderBranchInput() string { MarginLeft(1) if len(m.textInput.AvailableSuggestions()) > 0 && m.textInput.Value() == "" { - m.textInput.Placeholder = fmt.Sprintf("Type to switch branch (default: %s)", m.mainBranch) + if !m.state.Repository.HasFlows { + m.textInput.Placeholder = "Branch selection disabled - No triggerable workflows available" + } else { + m.textInput.Placeholder = fmt.Sprintf("Type to switch branch (default: %s)", m.state.Repository.Branch) + } } return style.Render(m.textInput.View()) @@ -409,3 +484,15 @@ func (m *ModelGithubWorkflow) updateTableDimensions() { m.tableTriggerableWorkflow.SetHeight(termHeight - 17) } } + +// ----------------------------------------------------------------------------- +// Tab Management +// ----------------------------------------------------------------------------- + +func (m *ModelGithubWorkflow) updateTabState() { + if !m.state.Repository.HasFlows { + m.skeleton.LockTab("trigger") + return + } + m.skeleton.UnlockTabs() +} From 9fa580ee63a95e795d641f027a1f8ce9cd601ef0 Mon Sep 17 00:00:00 2001 From: Engin Date: Tue, 7 Jan 2025 01:29:56 +0300 Subject: [PATCH 46/51] Refactor GitHub repository and workflow handling for improved performance and usability - Optimize workflow processing by counting only valid workflows before creating channels. - Enhance error handling with context timeouts for repository and workflow fetch operations. - Improve table rendering logic to maintain readability and prevent layout issues. - Update live mode functionality to ensure immediate updates and better user feedback. - Refactor search functionality to handle case-insensitive searches and restore original rows when empty. These changes enhance the responsiveness and user experience of the terminal handler components. --- internal/github/repository/repository.go | 23 ++-- internal/terminal/handler/ghrepository.go | 52 ++++++-- .../terminal/handler/ghworkflowhistory.go | 112 ++++++++++++++---- 3 files changed, 144 insertions(+), 43 deletions(-) diff --git a/internal/github/repository/repository.go b/internal/github/repository/repository.go index 2a291a0..a4d7955 100644 --- a/internal/github/repository/repository.go +++ b/internal/github/repository/repository.go @@ -184,28 +184,31 @@ func (r *Repo) GetTriggerableWorkflows(ctx context.Context, repository string) ( return nil, err } - // Create a buffered channel for results and errors - results := make(chan *Workflow, len(workflows.Workflows)) - errs := make(chan error, len(workflows.Workflows)) + // Count how many workflows we'll actually process + var validWorkflowCount int + for _, workflow := range workflows.Workflows { + if strings.HasPrefix(workflow.Path, ".github/workflows/") { + validWorkflowCount++ + } + } + + // Create buffered channels only for valid workflows + results := make(chan *Workflow, validWorkflowCount) + errs := make(chan error, validWorkflowCount) - // Filter workflows to only include those that are dispatchable and manually triggerable + // Only process workflows with valid paths for _, workflow := range workflows.Workflows { - // if workflow.Path starts with .github/workflows/ if strings.HasPrefix(workflow.Path, ".github/workflows/") { go r.workerGetTriggerableWorkflows(ctx, repository, workflow, results, errs) - } else { - // do not send but we created a channel for it - results <- nil } } // Collect the results and errors var result []Workflow var resultErrs []error - for range workflows.Workflows { + for i := 0; i < validWorkflowCount; i++ { select { case res := <-results: - // append only triggerable (dispatch) workflows if res != nil { result = append(result, *res) } diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index 6f3d3a3..b02afcc 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -236,7 +236,6 @@ func (m *ModelGithubRepository) View() string { ) } - // ----------------------------------------------------------------------------- // UI Rendering // ----------------------------------------------------------------------------- @@ -284,6 +283,10 @@ func (m *ModelGithubRepository) syncRepositories(ctx context.Context) { m.status.SetProgressMessage("Fetching repositories...") m.clearTables() + // Add timeout to prevent hanging + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + repos, err := m.fetchRepositories(ctx) if err != nil { m.handleFetchError(err) @@ -311,17 +314,33 @@ func (m *ModelGithubRepository) clearTables() { } func (m *ModelGithubRepository) updateTableDimensions() { + const minTableWidth = 60 // Minimum width to maintain readability + const tablePadding = 14 // Account for borders and margins + var tableWidth int for _, t := range tableColumnsGithubRepository { tableWidth += t.Width } - newTableColumns := tableColumnsGithubRepository - widthDiff := m.skeleton.GetTerminalWidth() - tableWidth + termWidth := m.skeleton.GetTerminalWidth() + if termWidth <= minTableWidth { + return // Prevent table from becoming too narrow + } + + newTableColumns := make([]table.Column, len(tableColumnsGithubRepository)) + copy(newTableColumns, tableColumnsGithubRepository) + + widthDiff := termWidth - tableWidth - tablePadding if widthDiff > 0 { - newTableColumns[0].Width += widthDiff - 14 + // Add extra width to repository name column + newTableColumns[0].Width += widthDiff m.tableGithubRepository.SetColumns(newTableColumns) - m.tableGithubRepository.SetHeight(m.skeleton.GetTerminalHeight() - 20) + + // Adjust height while maintaining some padding + maxHeight := m.skeleton.GetTerminalHeight() - 20 + if maxHeight > 0 { + m.tableGithubRepository.SetHeight(maxHeight) + } } } @@ -387,22 +406,26 @@ func (m *ModelGithubRepository) finalizeTableUpdate() { // ----------------------------------------------------------------------------- func (m *ModelGithubRepository) updateTableRowsBySearchBar() { - searchValue := m.textInput.Value() - rows := m.searchTableGithubRepository.Rows() + searchValue := strings.ToLower(m.textInput.Value()) + if searchValue == "" { + // If search is empty, restore original rows + m.tableGithubRepository.SetRows(m.searchTableGithubRepository.Rows()) + return + } - // Filter rows based on repository name + rows := m.searchTableGithubRepository.Rows() filteredRows := make([]table.Row, 0, len(rows)) + for _, row := range rows { - if strings.Contains(row[0], searchValue) { + if strings.Contains(strings.ToLower(row[0]), searchValue) { filteredRows = append(filteredRows, row) } } + m.tableGithubRepository.SetRows(filteredRows) if len(filteredRows) == 0 { m.clearSelectedRepository() } - - m.tableGithubRepository.SetRows(filteredRows) } func (m *ModelGithubRepository) clearSelectedRepository() { @@ -457,11 +480,16 @@ func (m *ModelGithubRepository) setupBrowserOption() { func (m *ModelGithubRepository) handleFetchError(err error) { if errors.Is(err, context.Canceled) { + m.status.SetDefaultMessage("Repository fetch cancelled") + return + } + if errors.Is(err, context.DeadlineExceeded) { + m.status.SetErrorMessage("Repository fetch timed out") return } m.status.SetError(err) - m.status.SetErrorMessage("Repositories cannot be listed") + m.status.SetErrorMessage(fmt.Sprintf("Failed to list repositories: %v", err)) } // ----------------------------------------------------------------------------- diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index db54f63..9b89457 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -219,10 +219,19 @@ func (m *ModelGithubWorkflowHistory) handleUpdateMsg(msg workflowHistoryUpdateMs // ----------------------------------------------------------------------------- func (m *ModelGithubWorkflowHistory) startLiveMode() { + ticker := time.NewTicker(m.liveModeInterval) go func() { - for range time.NewTicker(m.liveModeInterval).C { - if m.liveMode { - m.skeleton.TriggerUpdateWithMsg(workflowHistoryUpdateMsg{UpdateAfter: time.Nanosecond}) + defer ticker.Stop() + for { + select { + case <-ticker.C: + if m.liveMode { + m.skeleton.TriggerUpdateWithMsg(workflowHistoryUpdateMsg{ + UpdateAfter: time.Nanosecond, + }) + } + case <-m.syncWorkflowHistoryContext.Done(): + return } } }() @@ -230,13 +239,20 @@ func (m *ModelGithubWorkflowHistory) startLiveMode() { func (m *ModelGithubWorkflowHistory) toggleLiveMode() tea.Cmd { m.liveMode = !m.liveMode + + status := "Off" + message := "Live mode disabled" + if m.liveMode { - m.status.SetSuccessMessage("Live mode enabled") - m.skeleton.UpdateWidgetValue("live", "Live Mode: On") - } else { - m.status.SetSuccessMessage("Live mode disabled") - m.skeleton.UpdateWidgetValue("live", "Live Mode: Off") + status = "On" + message = "Live mode enabled" + // Trigger immediate update when enabling + go m.syncWorkflowHistory(m.syncWorkflowHistoryContext) } + + m.status.SetSuccessMessage(message) + m.skeleton.UpdateWidgetValue("live", fmt.Sprintf("Live Mode: %s", status)) + return nil } @@ -267,15 +283,39 @@ func (m *ModelGithubWorkflowHistory) handleRepositoryChange() tea.Cmd { func (m *ModelGithubWorkflowHistory) syncWorkflowHistory(ctx context.Context) { defer m.skeleton.TriggerUpdate() + // Add timeout to prevent hanging + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + m.initializeSyncState() + + // Check context before proceeding + if ctx.Err() != nil { + m.status.SetDefaultMessage("Operation cancelled") + return + } + workflowHistory, err := m.fetchWorkflowHistory(ctx) if err != nil { + m.handleFetchError(err) return } m.processWorkflowHistory(workflowHistory) } +func (m *ModelGithubWorkflowHistory) handleFetchError(err error) { + switch { + case errors.Is(err, context.Canceled): + m.status.SetDefaultMessage("Workflow history fetch cancelled") + case errors.Is(err, context.DeadlineExceeded): + m.status.SetErrorMessage("Workflow history fetch timed out") + default: + m.status.SetError(err) + m.status.SetErrorMessage(fmt.Sprintf("Failed to fetch workflow history: %v", err)) + } +} + func (m *ModelGithubWorkflowHistory) initializeSyncState() { m.tableReady = false m.status.Reset() @@ -385,28 +425,42 @@ func (m *ModelGithubWorkflowHistory) renderHelp() string { } func (m *ModelGithubWorkflowHistory) updateTableDimensions() { + const ( + minTableWidth = 80 // Minimum width to maintain readability + tablePadding = 18 // Account for borders and margins + minColumnWidth = 10 // Minimum width for any column + ) + termWidth := m.skeleton.GetTerminalWidth() termHeight := m.skeleton.GetTerminalHeight() + if termWidth <= minTableWidth { + return // Prevent table from becoming too narrow + } + var tableWidth int for _, t := range tableColumnsWorkflowHistory { tableWidth += t.Width } - newTableColumns := tableColumnsWorkflowHistory - widthDiff := termWidth - tableWidth + newTableColumns := make([]table.Column, len(tableColumnsWorkflowHistory)) + copy(newTableColumns, tableColumnsWorkflowHistory) + widthDiff := termWidth - tableWidth - tablePadding if widthDiff > 0 { - if m.updateRound%2 == 0 { - newTableColumns[0].Width += widthDiff - 18 - } else { - newTableColumns[1].Width += widthDiff - 18 - } - m.updateRound++ + // Distribute extra width between workflow name and action name columns + extraWidth := widthDiff / 2 + newTableColumns[0].Width = max(newTableColumns[0].Width+extraWidth, minColumnWidth) + newTableColumns[1].Width = max(newTableColumns[1].Width+extraWidth, minColumnWidth) + m.tableWorkflowHistory.SetColumns(newTableColumns) } - m.tableWorkflowHistory.SetHeight(termHeight - 17) + // Ensure reasonable table height + maxHeight := termHeight - 17 + if maxHeight > 0 { + m.tableWorkflowHistory.SetHeight(maxHeight) + } } // ----------------------------------------------------------------------------- @@ -453,20 +507,36 @@ func (m *ModelGithubWorkflowHistory) rerunFailedJobs() { } func (m *ModelGithubWorkflowHistory) rerunWorkflow() { + if m.selectedWorkflowID == 0 { + m.status.SetErrorMessage("No workflow selected") + return + } + m.status.SetProgressMessage("Re-running workflow...") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() - _, err := m.github.ReRunWorkflow(context.Background(), gu.ReRunWorkflowInput{ + _, err := m.github.ReRunWorkflow(ctx, gu.ReRunWorkflowInput{ Repository: m.selectedRepository.RepositoryName, WorkflowID: m.selectedWorkflowID, }) if err != nil { - m.status.SetError(err) - m.status.SetErrorMessage("Failed to re-run workflow") + if errors.Is(err, context.DeadlineExceeded) { + m.status.SetErrorMessage("Workflow re-run request timed out") + } else { + m.status.SetError(err) + m.status.SetErrorMessage(fmt.Sprintf("Failed to re-run workflow: %v", err)) + } return } - m.status.SetSuccessMessage("Re-ran workflow") + m.status.SetSuccessMessage("Workflow re-run initiated") + // Trigger refresh after short delay to show updated status + go func() { + time.Sleep(2 * time.Second) + m.syncWorkflowHistory(m.syncWorkflowHistoryContext) + }() } func (m *ModelGithubWorkflowHistory) cancelWorkflow() { From ac4de9a7dd22c653d729b06f1c73bc81b452c476 Mon Sep 17 00:00:00 2001 From: Engin Date: Tue, 7 Jan 2025 01:48:44 +0300 Subject: [PATCH 47/51] Minor changes --- internal/terminal/handler/ghtrigger.go | 17 +++++++++-------- internal/terminal/handler/ghworkflowhistory.go | 1 - 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index 672a86c..a343d87 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -8,6 +8,7 @@ import ( "time" "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/table" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" @@ -213,20 +214,20 @@ func (m *ModelGithubTrigger) initializeWorkflowSync() tea.Cmd { // ----------------------------------------------------------------------------- func (m *ModelGithubTrigger) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { - switch msg.String() { - case "up": + switch { + case msg.String() == "up": return m.handleUpKey() - case "down": + case msg.String() == "down": return m.handleDownKey() - case "ctrl+r", "ctrl+R": + case key.Matches(msg, m.Keys.Refresh): go m.syncWorkflowContent(m.syncWorkflowContext) - case "left": + case msg.String() == "left": m.handleLeftKey() - case "right": + case msg.String() == "right": m.handleRightKey() - case "tab": + case msg.String() == "tab": m.handleTabKey() - case "enter": + case msg.String() == "enter": if m.triggerFocused && m.isTriggerable { go m.triggerWorkflow() } diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index 9b89457..adc2f7f 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -36,7 +36,6 @@ type ModelGithubWorkflowHistory struct { // Table state tableReady bool tableStyle lipgloss.Style - updateRound int workflows []gu.Workflow lastRepository string From 7456da0f1c24145e80253c432190de2cc920454f Mon Sep 17 00:00:00 2001 From: Engin Date: Tue, 7 Jan 2025 01:56:32 +0300 Subject: [PATCH 48/51] Update input size --- internal/terminal/handler/ghtrigger.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index a343d87..bdb5e3a 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -628,7 +628,7 @@ func (m *ModelGithubTrigger) getSelectorStyle() lipgloss.Style { Border(lipgloss.NormalBorder()). BorderForeground(lipgloss.Color("#3b698f")). Padding(0, 1). - Width(m.skeleton.GetTerminalWidth() - 18). + Width(m.skeleton.GetTerminalWidth() - 17). MarginLeft(1) } From cac6e632a1d67ed42906a2311aa89f213c673e43 Mon Sep 17 00:00:00 2001 From: Engin Date: Tue, 7 Jan 2025 02:04:09 +0300 Subject: [PATCH 49/51] update readme --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 8a5feb6..fb042cd 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,17 @@ GAMA is a powerful terminal-based user interface tool designed to streamline the GAMA Go Report Card GAMA Licence +## Table of Contents +- [Key Features](#key-features) +- [Live Mode](#live-mode) +- [Getting Started](#getting-started) + - [Prerequisites](#prerequisites) + - [Configuration](#configuration) +- [Build & Installation](#build--installation) +- [Contributing](#contributing) +- [License](#license) +- [Contact & Author](#contact--author) + ![gama demo](docs/gama.gif) ## Key Features @@ -14,6 +25,18 @@ GAMA is a powerful terminal-based user interface tool designed to streamline the - **Workflow History**: Conveniently list all historical runs of workflows in a repository. - **Discoverability**: Easily list all triggerable (dispatchable) workflows in a repository. - **Workflow Management**: Trigger specific workflows with custom inputs. +- **Live Updates**: Automatically refresh workflow status at configurable intervals. +- **Docker Support**: Run directly from a container for easy deployment. + +### Live Mode + +GAMA includes a live mode feature that automatically refreshes the workflow status at regular intervals: + +- **Toggle Live Updates**: Press `ctrl+l` to turn live mode on/off +- **Auto-start**: Set `settings.live_mode.enabled: true` to start GAMA with live mode enabled +- **Refresh Interval**: Configure how often the view updates with `settings.live_mode.interval` (e.g., "15s", "1m") + +Live mode is particularly useful when monitoring ongoing workflow runs, as it eliminates the need for manual refreshing. ## Getting Started @@ -36,8 +59,14 @@ keys: switch_tab_left: shift+left quit: ctrl+c refresh: ctrl+r + live_mode: ctrl+l # Toggle live mode on/off enter: enter tab: tab + +settings: + live_mode: + enabled: true # Enable live mode at startup + interval: 15s # Refresh interval for live updates ``` #### Environment Variable Configuration From 1f7227dfba32d00dec52ef1f780d4d986279ca13 Mon Sep 17 00:00:00 2001 From: Engin Date: Tue, 7 Jan 2025 02:09:55 +0300 Subject: [PATCH 50/51] Update go version --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 60b99e3..5d2b915 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -17,7 +17,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v3 with: - go-version: '1.22' + go-version: '1.23' - name: Get the latest tag id: get_latest_tag From d9e04124518856d44515baf3f555787af895252c Mon Sep 17 00:00:00 2001 From: Engin Date: Tue, 7 Jan 2025 02:28:59 +0300 Subject: [PATCH 51/51] Minor changes --- internal/terminal/handler/ghinformation.go | 6 ++--- internal/terminal/handler/ghrepository.go | 24 +++++++------------ internal/terminal/handler/ghtrigger.go | 6 ++--- internal/terminal/handler/ghworkflow.go | 6 ++--- .../terminal/handler/ghworkflowhistory.go | 8 +++---- internal/terminal/handler/handler.go | 5 +++- internal/terminal/handler/status.go | 1 + internal/terminal/handler/table.go | 1 - internal/terminal/handler/taboptions.go | 21 +++++++--------- internal/terminal/handler/types.go | 11 ++------- 10 files changed, 36 insertions(+), 53 deletions(-) diff --git a/internal/terminal/handler/ghinformation.go b/internal/terminal/handler/ghinformation.go index 1b5fcdc..c6fd829 100644 --- a/internal/terminal/handler/ghinformation.go +++ b/internal/terminal/handler/ghinformation.go @@ -40,18 +40,18 @@ type ModelInfo struct { // Constructor & Initialization // ----------------------------------------------------------------------------- -func SetupModelInfo(sk *skeleton.Skeleton, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { +func SetupModelInfo(s *skeleton.Skeleton, githubUseCase gu.UseCase, version pkgversion.Version) *ModelInfo { const releaseURL = "https://github.com/termkit/gama/releases" return &ModelInfo{ // Initialize core dependencies - skeleton: sk, + skeleton: s, github: githubUseCase, version: version, // Initialize UI components help: help.New(), - status: SetupModelStatus(sk), + status: SetupModelStatus(s), keys: githubInformationKeys, // Initialize application state diff --git a/internal/terminal/handler/ghrepository.go b/internal/terminal/handler/ghrepository.go index b02afcc..e876409 100644 --- a/internal/terminal/handler/ghrepository.go +++ b/internal/terminal/handler/ghrepository.go @@ -53,18 +53,18 @@ type ModelGithubRepository struct { // Constructor & Initialization // ----------------------------------------------------------------------------- -func SetupModelGithubRepository(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubRepository { +func SetupModelGithubRepository(s *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubRepository { m := &ModelGithubRepository{ // Initialize core dependencies - skeleton: sk, + skeleton: s, github: githubUseCase, // Initialize UI components help: help.New(), Keys: githubRepositoryKeys, - status: SetupModelStatus(sk), + status: SetupModelStatus(s), textInput: setupTextInput(), - modelTabOptions: NewOptions(sk, SetupModelStatus(sk)), + modelTabOptions: NewOptions(s, SetupModelStatus(s)), // Initialize state selectedRepository: NewSelectedRepository(), @@ -357,25 +357,17 @@ func (m *ModelGithubRepository) resetTableCursors() { func (m *ModelGithubRepository) updateRepositoryData(repos *gu.ListRepositoriesOutput) { if len(repos.Repositories) == 0 { - m.handleEmptyRepositories() + m.modelTabOptions.SetStatus(StatusNone) + m.status.SetDefaultMessage("No repositories found") + m.textInput.Blur() return } - m.updateWidgetCount(len(repos.Repositories)) + m.skeleton.UpdateWidgetValue("repositories", fmt.Sprintf("Repository Count: %d", len(repos.Repositories))) m.updateTableRows(repos.Repositories) m.finalizeTableUpdate() } -func (m *ModelGithubRepository) handleEmptyRepositories() { - m.modelTabOptions.SetStatus(StatusNone) - m.status.SetDefaultMessage("No repositories found") - m.textInput.Blur() -} - -func (m *ModelGithubRepository) updateWidgetCount(count int) { - m.skeleton.UpdateWidgetValue("repositories", fmt.Sprintf("Repository Count: %d", count)) -} - func (m *ModelGithubRepository) updateTableRows(repositories []gu.GithubRepository) { rows := make([]table.Row, 0, len(repositories)) for _, repo := range repositories { diff --git a/internal/terminal/handler/ghtrigger.go b/internal/terminal/handler/ghtrigger.go index bdb5e3a..e9d8392 100644 --- a/internal/terminal/handler/ghtrigger.go +++ b/internal/terminal/handler/ghtrigger.go @@ -68,16 +68,16 @@ type ModelGithubTrigger struct { // Constructor & Initialization // ----------------------------------------------------------------------------- -func SetupModelGithubTrigger(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubTrigger { +func SetupModelGithubTrigger(s *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubTrigger { m := &ModelGithubTrigger{ // Initialize core dependencies - skeleton: sk, + skeleton: s, github: githubUseCase, // Initialize UI components help: help.New(), Keys: githubTriggerKeys, - status: SetupModelStatus(sk), + status: SetupModelStatus(s), textInput: setupTriggerInput(), tableTrigger: setupTriggerTable(), diff --git a/internal/terminal/handler/ghworkflow.go b/internal/terminal/handler/ghworkflow.go index 95db5ff..ab0d40c 100644 --- a/internal/terminal/handler/ghworkflow.go +++ b/internal/terminal/handler/ghworkflow.go @@ -63,16 +63,16 @@ type ModelGithubWorkflow struct { // Constructor & Initialization // ----------------------------------------------------------------------------- -func SetupModelGithubWorkflow(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflow { +func SetupModelGithubWorkflow(s *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflow { m := &ModelGithubWorkflow{ // Initialize core dependencies - skeleton: sk, + skeleton: s, github: githubUseCase, // Initialize UI components help: help.New(), keys: githubWorkflowKeys, - status: SetupModelStatus(sk), + status: SetupModelStatus(s), textInput: setupBranchInput(), // Initialize state diff --git a/internal/terminal/handler/ghworkflowhistory.go b/internal/terminal/handler/ghworkflowhistory.go index adc2f7f..86e649b 100644 --- a/internal/terminal/handler/ghworkflowhistory.go +++ b/internal/terminal/handler/ghworkflowhistory.go @@ -62,7 +62,7 @@ type workflowHistoryUpdateMsg struct { // Constructor & Initialization // ----------------------------------------------------------------------------- -func SetupModelGithubWorkflowHistory(sk *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { +func SetupModelGithubWorkflowHistory(s *skeleton.Skeleton, githubUseCase gu.UseCase) *ModelGithubWorkflowHistory { cfg, err := config.LoadConfig() if err != nil { panic(fmt.Sprintf("failed to load config: %v", err)) @@ -70,14 +70,14 @@ func SetupModelGithubWorkflowHistory(sk *skeleton.Skeleton, githubUseCase gu.Use m := &ModelGithubWorkflowHistory{ // Initialize core dependencies - skeleton: sk, + skeleton: s, github: githubUseCase, // Initialize UI components Help: help.New(), keys: githubWorkflowHistoryKeys, - status: SetupModelStatus(sk), - modelTabOptions: NewOptions(sk, SetupModelStatus(sk)), + status: SetupModelStatus(s), + modelTabOptions: NewOptions(s, SetupModelStatus(s)), // Initialize state selectedRepository: NewSelectedRepository(), diff --git a/internal/terminal/handler/handler.go b/internal/terminal/handler/handler.go index a058817..91d33bf 100644 --- a/internal/terminal/handler/handler.go +++ b/internal/terminal/handler/handler.go @@ -23,7 +23,10 @@ func SetupTerminal(githubUseCase gu.UseCase, version pkgversion.Version) tea.Mod s.AddPage("workflow", "Workflow", SetupModelGithubWorkflow(s, githubUseCase)) s.AddPage("trigger", "Trigger", SetupModelGithubTrigger(s, githubUseCase)) - s.SetBorderColor("#ff0055").SetActiveTabBorderColor("#ff0055").SetInactiveTabBorderColor("#82636f").SetWidgetBorderColor("#ff0055") + s.SetBorderColor("#ff0055"). + SetActiveTabBorderColor("#ff0055"). + SetInactiveTabBorderColor("#82636f"). + SetWidgetBorderColor("#ff0055") if cfg.Settings.LiveMode.Enabled { s.AddWidget("live", "Live Mode: On") diff --git a/internal/terminal/handler/status.go b/internal/terminal/handler/status.go index 05897f6..fa8c8ac 100644 --- a/internal/terminal/handler/status.go +++ b/internal/terminal/handler/status.go @@ -10,6 +10,7 @@ import ( type ModelStatus struct { skeleton *skeleton.Skeleton + // err is hold the error err error diff --git a/internal/terminal/handler/table.go b/internal/terminal/handler/table.go index ce59527..65dac36 100644 --- a/internal/terminal/handler/table.go +++ b/internal/terminal/handler/table.go @@ -16,7 +16,6 @@ var tableColumnsTrigger = []table.Column{ {Title: "Type", Width: 6}, {Title: "Key", Width: 24}, {Title: "Default", Width: 16}, - //{Title: "Description", Width: 64}, {Title: "Value", Width: 44}, } diff --git a/internal/terminal/handler/taboptions.go b/internal/terminal/handler/taboptions.go index f7287b8..5ac3c21 100644 --- a/internal/terminal/handler/taboptions.go +++ b/internal/terminal/handler/taboptions.go @@ -14,8 +14,6 @@ import ( type ModelTabOptions struct { skeleton *skeleton.Skeleton - Style lipgloss.Style - status *ModelStatus previousStatus ModelStatus modelLock bool @@ -52,15 +50,6 @@ func (o OptionStatus) String() string { } func NewOptions(sk *skeleton.Skeleton, modelStatus *ModelStatus) *ModelTabOptions { - var b = lipgloss.RoundedBorder() - b.Right = "├" - b.Left = "┤" - - var OptionsStyle = lipgloss.NewStyle(). - Foreground(lipgloss.Color("15")). - Align(lipgloss.Center).Padding(0, 1, 0, 1). - Border(b) - var initialOptions = []string{ StatusWait.String(), } @@ -73,7 +62,6 @@ func NewOptions(sk *skeleton.Skeleton, modelStatus *ModelStatus) *ModelTabOption return &ModelTabOptions{ skeleton: sk, - Style: OptionsStyle, options: initialOptions, optionsAction: initialOptionsAction, optionsWithFunc: optionsWithFunc, @@ -113,7 +101,14 @@ func (o *ModelTabOptions) Update(msg tea.Msg) (*ModelTabOptions, tea.Cmd) { } func (o *ModelTabOptions) View() string { - var style = o.Style.Foreground(lipgloss.Color("15")) + var b = lipgloss.RoundedBorder() + b.Right = "├" + b.Left = "┤" + + var style = lipgloss.NewStyle(). + Foreground(lipgloss.Color("15")). + Align(lipgloss.Center).Padding(0, 1, 0, 1). + Border(b).Foreground(lipgloss.Color("15")) var opts []string opts = append(opts, " ") diff --git a/internal/terminal/handler/types.go b/internal/terminal/handler/types.go index 0424d92..27a48f5 100644 --- a/internal/terminal/handler/types.go +++ b/internal/terminal/handler/types.go @@ -6,7 +6,8 @@ import ( "github.com/charmbracelet/lipgloss" ) -// Types +// SelectedRepository is a struct that holds the selected repository, workflow, and branch +// It is a shared state between the different tabs type SelectedRepository struct { RepositoryName string WorkflowName string @@ -17,14 +18,6 @@ type SelectedRepository struct { const ( MinTerminalWidth = 102 MinTerminalHeight = 24 - - DefaultTableHeight = 13 - DefaultTerminalWidth = 80 - DefaultTerminalHeight = 24 - - ColorPrimary = "#3b698f" - ColorSecondary = "#ff0055" - ColorError = "#ff0000" ) // Styles