1package tui
2
3import (
4 "github.com/charmbracelet/bubbles/key"
5 tea "github.com/charmbracelet/bubbletea"
6 "github.com/charmbracelet/lipgloss"
7 "github.com/kujtimiihoxha/termai/internal/tui/components/core"
8 "github.com/kujtimiihoxha/termai/internal/tui/layout"
9 "github.com/kujtimiihoxha/termai/internal/tui/page"
10 "github.com/kujtimiihoxha/termai/internal/tui/util"
11)
12
13type keyMap struct {
14 Logs key.Binding
15 Return key.Binding
16 Back key.Binding
17 Quit key.Binding
18 Help key.Binding
19}
20
21var keys = keyMap{
22 Logs: key.NewBinding(
23 key.WithKeys("L"),
24 key.WithHelp("L", "logs"),
25 ),
26 Return: key.NewBinding(
27 key.WithKeys("esc"),
28 key.WithHelp("esc", "close"),
29 ),
30 Back: key.NewBinding(
31 key.WithKeys("backspace"),
32 key.WithHelp("backspace", "back"),
33 ),
34 Quit: key.NewBinding(
35 key.WithKeys("ctrl+c", "q"),
36 key.WithHelp("ctrl+c/q", "quit"),
37 ),
38 Help: key.NewBinding(
39 key.WithKeys("?"),
40 key.WithHelp("?", "toggle help"),
41 ),
42}
43
44type appModel struct {
45 width, height int
46 currentPage page.PageID
47 previousPage page.PageID
48 pages map[page.PageID]tea.Model
49 loadedPages map[page.PageID]bool
50 status tea.Model
51 help core.HelpCmp
52 showHelp bool
53}
54
55func (a appModel) Init() tea.Cmd {
56 cmd := a.pages[a.currentPage].Init()
57 a.loadedPages[a.currentPage] = true
58 return cmd
59}
60
61func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
62 switch msg := msg.(type) {
63 case tea.WindowSizeMsg:
64 msg.Height -= 1 // Make space for the status bar
65 a.width, a.height = msg.Width, msg.Height
66
67 a.status, _ = a.status.Update(msg)
68
69 uh, _ := a.help.Update(msg)
70 a.help = uh.(core.HelpCmp)
71
72 p, cmd := a.pages[a.currentPage].Update(msg)
73 a.pages[a.currentPage] = p
74 return a, cmd
75 case util.InfoMsg:
76 a.status, _ = a.status.Update(msg)
77 case util.ErrorMsg:
78 a.status, _ = a.status.Update(msg)
79 case tea.KeyMsg:
80 switch {
81 case key.Matches(msg, keys.Quit):
82 return a, tea.Quit
83 case key.Matches(msg, keys.Back):
84 if a.previousPage != "" {
85 return a, a.moveToPage(a.previousPage)
86 }
87 case key.Matches(msg, keys.Return):
88 if a.showHelp {
89 a.ToggleHelp()
90 return a, nil
91 }
92 return a, nil
93 case key.Matches(msg, keys.Logs):
94 return a, a.moveToPage(page.LogsPage)
95 case key.Matches(msg, keys.Help):
96 a.ToggleHelp()
97 return a, nil
98 }
99 }
100 p, cmd := a.pages[a.currentPage].Update(msg)
101 a.pages[a.currentPage] = p
102 return a, cmd
103}
104
105func (a *appModel) ToggleHelp() {
106 if a.showHelp {
107 a.showHelp = false
108 a.height += a.help.Height()
109 } else {
110 a.showHelp = true
111 a.height -= a.help.Height()
112 }
113
114 if sizable, ok := a.pages[a.currentPage].(layout.Sizeable); ok {
115 sizable.SetSize(a.width, a.height)
116 }
117}
118
119func (a *appModel) moveToPage(pageID page.PageID) tea.Cmd {
120 var cmd tea.Cmd
121 if _, ok := a.loadedPages[pageID]; !ok {
122 cmd = a.pages[pageID].Init()
123 a.loadedPages[pageID] = true
124 }
125 a.previousPage = a.currentPage
126 a.currentPage = pageID
127 if sizable, ok := a.pages[a.currentPage].(layout.Sizeable); ok {
128 sizable.SetSize(a.width, a.height)
129 }
130
131 return cmd
132}
133
134func (a appModel) View() string {
135 components := []string{
136 a.pages[a.currentPage].View(),
137 }
138
139 if a.showHelp {
140 bindings := layout.KeyMapToSlice(keys)
141 if p, ok := a.pages[a.currentPage].(layout.Bindings); ok {
142 bindings = append(bindings, p.BindingKeys()...)
143 }
144 a.help.SetBindings(bindings)
145 components = append(components, a.help.View())
146 }
147
148 components = append(components, a.status.View())
149
150 return lipgloss.JoinVertical(lipgloss.Top, components...)
151}
152
153func New() tea.Model {
154 return &appModel{
155 currentPage: page.ReplPage,
156 loadedPages: make(map[page.PageID]bool),
157 status: core.NewStatusCmp(),
158 help: core.NewHelpCmp(),
159 pages: map[page.PageID]tea.Model{
160 page.LogsPage: page.NewLogsPage(),
161 page.InitPage: page.NewInitPage(),
162 page.ReplPage: page.NewReplPage(),
163 },
164 }
165}