Skip to content

Commit 456e872

Browse files
committed
Merge branch 'refactor-v1'
2 parents a1e4c59 + d9c4003 commit 456e872

34 files changed

+3955
-3190
lines changed

cmd/rivet/root.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,14 @@ func runViewWithConfig(cfg *config.Config, configPath string) error {
188188
interval = cfg.GetRefreshInterval()
189189
}
190190

191-
opts := tui.MenuOptions{
191+
opts := tui.AppOptions{
192192
StatePath: statePath,
193193
NoRestoreState: noState,
194194
RefreshInterval: interval,
195195
}
196196

197-
model := tui.NewMenuModel(cfg, configPath, gh, opts)
198-
if err := tui.RunMenu(model); err != nil {
197+
app := tui.NewApp(cfg, configPath, gh, opts)
198+
if err := tui.RunApp(app); err != nil {
199199
return err
200200
}
201201

internal/tui/app.go

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
package tui
2+
3+
import (
4+
"time"
5+
6+
tea "github.com/charmbracelet/bubbletea"
7+
8+
"github.com/Cloudsky01/gh-rivet/internal/config"
9+
"github.com/Cloudsky01/gh-rivet/internal/github"
10+
"github.com/Cloudsky01/gh-rivet/internal/state"
11+
"github.com/Cloudsky01/gh-rivet/internal/tui/components"
12+
"github.com/Cloudsky01/gh-rivet/internal/tui/theme"
13+
"github.com/Cloudsky01/gh-rivet/pkg/models"
14+
)
15+
16+
type workflowRunsMsg struct {
17+
runs []models.GHRun
18+
err error
19+
}
20+
21+
type refreshTickMsg struct {
22+
timestamp time.Time
23+
}
24+
25+
type ViewMode int
26+
27+
const (
28+
ViewGroups ViewMode = iota
29+
ViewRuns
30+
)
31+
32+
type FocusArea int
33+
34+
const (
35+
FocusSidebar FocusArea = iota
36+
FocusMain
37+
)
38+
39+
type App struct {
40+
config *config.Config
41+
configPath string
42+
statePath string
43+
gh *github.Client
44+
45+
theme *theme.Theme
46+
47+
sidebar components.Sidebar
48+
navList components.List
49+
runsTable *components.RunsTable
50+
search components.Search
51+
cmdPalette components.CmdPalette
52+
helpOverlay components.HelpOverlay
53+
toaster components.Toaster
54+
spinner components.Spinner
55+
statusBar components.StatusBar
56+
helpBar components.HelpBar
57+
58+
groupPath []*config.Group
59+
selectedWorkflow string
60+
selectedGroup *config.Group
61+
62+
viewMode ViewMode
63+
focusArea FocusArea
64+
showSidebar bool
65+
width int
66+
height int
67+
68+
workflowRuns []models.GHRun
69+
loading bool
70+
err error
71+
72+
refreshInterval int
73+
refreshTicker *time.Ticker
74+
autoRefreshEnabled bool
75+
}
76+
77+
type AppOptions struct {
78+
StartWithPinned bool
79+
StatePath string
80+
NoRestoreState bool
81+
RefreshInterval int
82+
}
83+
84+
// MenuOptions is deprecated, use AppOptions instead
85+
type MenuOptions struct {
86+
StartWithPinned bool
87+
StatePath string
88+
NoRestoreState bool
89+
RefreshInterval int
90+
}
91+
92+
func NewApp(cfg *config.Config, configPath string, gh *github.Client, opts AppOptions) *App {
93+
t := theme.Default()
94+
95+
statePath := opts.StatePath
96+
if statePath == "" {
97+
statePath = state.DefaultStatePath(configPath)
98+
}
99+
100+
app := &App{
101+
config: cfg,
102+
configPath: configPath,
103+
statePath: statePath,
104+
gh: gh,
105+
theme: t,
106+
sidebar: components.NewSidebar(t),
107+
navList: components.NewList(t, "📁 Groups"),
108+
runsTable: components.NewRunsTablePtr(t),
109+
search: components.NewSearch(t),
110+
cmdPalette: components.NewCmdPalette(t),
111+
helpOverlay: components.NewHelpOverlay(t),
112+
toaster: components.NewToaster(t),
113+
spinner: components.NewSpinner(t),
114+
statusBar: components.NewStatusBar(t),
115+
helpBar: components.NewHelpBar(t),
116+
groupPath: []*config.Group{},
117+
viewMode: ViewGroups,
118+
focusArea: FocusMain,
119+
showSidebar: true,
120+
refreshInterval: opts.RefreshInterval,
121+
autoRefreshEnabled: opts.RefreshInterval > 0,
122+
}
123+
124+
app.search.SetSearchFunc(func(query string) []components.SearchResult {
125+
return app.performGlobalSearch(query)
126+
})
127+
128+
app.setupCommands()
129+
app.refreshNavList()
130+
app.refreshPinnedList()
131+
app.updateStatusBar()
132+
133+
if !opts.NoRestoreState {
134+
app.restoreState()
135+
}
136+
137+
if opts.StartWithPinned && len(cfg.GetAllPinnedWorkflows()) > 0 {
138+
app.focusArea = FocusSidebar
139+
}
140+
141+
app.updateFocus()
142+
143+
return app
144+
}
145+
146+
func (a *App) Init() tea.Cmd {
147+
return nil
148+
}
149+
150+
func (a *App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
151+
var cmds []tea.Cmd
152+
153+
switch msg := msg.(type) {
154+
case tea.WindowSizeMsg:
155+
return a.handleResize(msg)
156+
157+
case tea.KeyMsg:
158+
return a.handleKey(msg)
159+
160+
case workflowRunsMsg:
161+
a.loading = false
162+
a.spinner.Stop()
163+
if msg.err != nil {
164+
a.err = msg.err
165+
a.runsTable.SetError(msg.err)
166+
cmds = append(cmds, a.toaster.Error("Failed to load runs"))
167+
} else {
168+
a.workflowRuns = msg.runs
169+
a.runsTable.SetRuns(msg.runs, a.selectedWorkflow)
170+
}
171+
a.runsTable.SetLoading(false)
172+
cmds = append(cmds, a.getRefreshTickerCmd())
173+
return a, tea.Batch(cmds...)
174+
175+
case refreshTickMsg:
176+
if a.selectedWorkflow != "" && !a.loading {
177+
a.loading = true
178+
a.runsTable.SetLoading(true)
179+
return a, tea.Batch(a.fetchWorkflowRunsCmd, a.getRefreshTickerCmd())
180+
}
181+
return a, a.getRefreshTickerCmd()
182+
183+
case components.ToastExpiredMsg:
184+
a.toaster.Update(msg)
185+
return a, nil
186+
187+
default:
188+
if a.spinner.IsActive() {
189+
cmd := a.spinner.Update(msg)
190+
if cmd != nil {
191+
return a, cmd
192+
}
193+
}
194+
}
195+
196+
return a, nil
197+
}
198+
199+
func (a *App) View() string {
200+
if a.width == 0 || a.height == 0 {
201+
return ""
202+
}
203+
204+
if a.helpOverlay.IsActive() {
205+
return a.helpOverlay.View()
206+
}
207+
208+
if a.cmdPalette.IsActive() {
209+
return a.cmdPalette.View()
210+
}
211+
212+
if a.search.IsActive() {
213+
return a.search.View()
214+
}
215+
216+
return a.renderLayout()
217+
}
218+
219+
func (a *App) handleResize(msg tea.WindowSizeMsg) (*App, tea.Cmd) {
220+
a.width = msg.Width
221+
a.height = msg.Height
222+
223+
barHeight := 2
224+
panelHeight := a.height - barHeight - 2
225+
226+
sidebarWidth := 0
227+
if a.showSidebar {
228+
sidebarWidth = max(25, a.width/5)
229+
}
230+
mainWidth := a.width - sidebarWidth - 2
231+
232+
a.sidebar.SetSize(sidebarWidth-2, panelHeight)
233+
a.navList.SetSize(mainWidth-2, panelHeight)
234+
a.runsTable.SetSize(mainWidth-2, panelHeight)
235+
a.search.SetSize(a.width, a.height)
236+
a.cmdPalette.SetSize(a.width, a.height)
237+
a.helpOverlay.SetSize(a.width, a.height)
238+
a.toaster.SetWidth(a.width)
239+
a.statusBar.SetSize(a.width)
240+
a.helpBar.SetSize(a.width)
241+
242+
return a, nil
243+
}
244+
245+
func (a *App) selectNavItem(item *components.ListItem) (*App, tea.Cmd) {
246+
navItem, ok := item.Data.(*navItemData)
247+
if !ok {
248+
return a, nil
249+
}
250+
251+
if navItem.isGroup {
252+
if navItem.group != nil {
253+
a.groupPath = append(a.groupPath, navItem.group)
254+
a.navList.ClearFilter()
255+
a.refreshNavList()
256+
a.saveState()
257+
}
258+
return a, nil
259+
}
260+
261+
return a.selectWorkflow(navItem.workflowName, nil)
262+
}
263+
264+
func (a *App) selectWorkflowFromSidebar(item *components.PinnedItem) (*App, tea.Cmd) {
265+
group, _ := item.Data.(*config.Group)
266+
return a.selectWorkflow(item.WorkflowName, group)
267+
}
268+
269+
func (a *App) selectWorkflow(name string, group *config.Group) (*App, tea.Cmd) {
270+
a.selectedWorkflow = name
271+
a.selectedGroup = group
272+
a.loading = true
273+
a.viewMode = ViewRuns
274+
a.runsTable.SetVisible(true)
275+
a.runsTable.SetLoading(true)
276+
a.focusArea = FocusMain
277+
a.updateFocus()
278+
a.startRefreshTicker()
279+
a.updateStatusBar()
280+
a.saveState()
281+
return a, tea.Batch(a.spinner.Start("Loading runs..."), a.fetchWorkflowRunsCmd)
282+
}
283+
284+
func RunApp(app *App) error {
285+
p := tea.NewProgram(app, tea.WithAltScreen())
286+
if _, err := p.Run(); err != nil {
287+
return err
288+
}
289+
return nil
290+
}
291+
292+
func NewAppFromMenuOptions(cfg *config.Config, configPath string, gh *github.Client, opts MenuOptions) *App {
293+
return NewApp(cfg, configPath, gh, AppOptions{
294+
StartWithPinned: opts.StartWithPinned,
295+
StatePath: opts.StatePath,
296+
NoRestoreState: opts.NoRestoreState,
297+
RefreshInterval: opts.RefreshInterval,
298+
})
299+
}

0 commit comments

Comments
 (0)