From ee1bfd40f92f089a297bbfce8da2e89ce9adece6 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Wed, 5 Nov 2025 17:36:50 +0100 Subject: [PATCH 01/46] fixed selection source --- internal/tui/views/endpoints-view.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index dec2343..371531f 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -132,7 +132,7 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin config.GetItemsFunc = epListFunc config.ItemMapper = itemMapperEp config.AdditionalKeymaps = keybinds - config.Source = "collections" + config.Source = "endpoints" view.list = optionsProvider.NewOptionsProvider(config) From 2ab1b7d888b6503fe90047c0fc5ca7af58714fd4 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 7 Nov 2025 00:13:47 +0100 Subject: [PATCH 02/46] new delegate created for the endpoints page --- internal/tui/views/collections-view-helper.go | 6 +-- internal/tui/views/collections-view.go | 2 +- internal/tui/views/endpoints-view-helper.go | 45 +++++++++++++++++++ internal/tui/views/endpoints-view.go | 2 +- 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 internal/tui/views/endpoints-view-helper.go diff --git a/internal/tui/views/collections-view-helper.go b/internal/tui/views/collections-view-helper.go index 410b0e0..f197ad7 100644 --- a/internal/tui/views/collections-view-helper.go +++ b/internal/tui/views/collections-view-helper.go @@ -7,7 +7,7 @@ import ( "github.com/maniac-en/req/internal/tui/styles" ) -func createDelegate() list.DefaultDelegate { +func createCollectionDelegate() list.ItemDelegate { d := list.NewDefaultDelegate() d.Styles.SelectedTitle = styles.SelectedListStyle @@ -16,14 +16,14 @@ func createDelegate() list.DefaultDelegate { return d } -func defaultListConfig[T, U any](binds *keybinds.ListKeyMap) *optionsProvider.ListConfig[T, U] { +func defaultListConfig[T, U any](binds *keybinds.ListKeyMap, delegateCreator func() list.ItemDelegate) *optionsProvider.ListConfig[T, U] { config := optionsProvider.ListConfig[T, U]{ ShowPagination: true, ShowStatusBar: false, ShowHelp: false, ShowTitle: false, FilteringEnabled: true, - Delegate: createDelegate(), + Delegate: delegateCreator(), KeyMap: list.KeyMap{ CursorUp: binds.CursorUp, CursorDown: binds.CursorDown, diff --git a/internal/tui/views/collections-view.go b/internal/tui/views/collections-view.go index 4659151..0c56a39 100644 --- a/internal/tui/views/collections-view.go +++ b/internal/tui/views/collections-view.go @@ -132,7 +132,7 @@ func itemMapper(items []collections.CollectionEntity, endpointsManager *endpoint func NewCollectionsView(collManager *collections.CollectionsManager, endpointsManager *endpoints.EndpointsManager, order int) *CollectionsView { keybinds := keybinds.NewListKeyMap() - config := defaultListConfig[collections.CollectionEntity, string](keybinds) + config := defaultListConfig[collections.CollectionEntity, string](keybinds, createCollectionDelegate) config.GetItemsFunc = collManager.List config.ItemMapper = func(items []collections.CollectionEntity) []list.Item { diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go new file mode 100644 index 0000000..0e856aa --- /dev/null +++ b/internal/tui/views/endpoints-view-helper.go @@ -0,0 +1,45 @@ +package views + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/maniac-en/req/internal/tui/components/OptionsProvider" +) + +func createEndpointsDelegate() list.ItemDelegate { + d := epItemDelegate{} + + // d.Styles.SelectedTitle = styles.SelectedListStyle + // d.Styles.SelectedDesc = styles.SelectedListStyle + + return d +} + +type epItemDelegate struct{} + +func (e epItemDelegate) Height() int { return 1 } +func (e epItemDelegate) Spacing() int { return 1 } +func (e epItemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } +func (e epItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + item, ok := listItem.(optionsProvider.Option) + + if !ok { + return + } + + str := fmt.Sprintf("%s %s", item.Subtext, item.Name) + + fn := lipgloss.NewStyle().Render + if index == m.Index() { + fn = func(s ...string) string { + return lipgloss.NewStyle().Render("> " + strings.Join(s, " ")) + } + } + + fmt.Fprint(w, fn(str)) +} diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 371531f..74ab07b 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -123,7 +123,7 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin } keybinds := keybinds.NewListKeyMap() - config := defaultListConfig[endpoints.EndpointEntity, database.Endpoint](keybinds) + config := defaultListConfig[endpoints.EndpointEntity, database.Endpoint](keybinds, createEndpointsDelegate) epListFunc := func(ctx context.Context) ([]endpoints.EndpointEntity, error) { return epManager.ListByCollection(ctx, view.collection.ID) From 614311e435dfac590e64fe552ba502bc6c21c8d3 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 7 Nov 2025 14:45:13 +0100 Subject: [PATCH 03/46] page up/down binds removed, keybind wording made better, breadcrumbs fixed, ep list rendering changed as discussed in issue #69 --- internal/tui/keybinds/keys.go | 10 +++++----- internal/tui/styles/collections-styles.go | 5 +++-- internal/tui/views/endpoints-view-helper.go | 14 +++++++++----- internal/tui/views/endpoints-view.go | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/internal/tui/keybinds/keys.go b/internal/tui/keybinds/keys.go index d04f267..f731a98 100644 --- a/internal/tui/keybinds/keys.go +++ b/internal/tui/keybinds/keys.go @@ -36,12 +36,12 @@ var Keys = Keymaps{ key.WithHelp("↓/j", "down"), ), PrevPage: key.NewBinding( - key.WithKeys("left", "h", "pgup"), - key.WithHelp("←/h/pgup", "prev page"), + key.WithKeys("left", "h"), + key.WithHelp("←/h", "prev page"), ), NextPage: key.NewBinding( key.WithKeys("right", "l", "pgdown"), - key.WithHelp("→/l/pgdn", "next page"), + key.WithHelp("→/l", "next page"), ), Filter: key.NewBinding( key.WithKeys("/"), @@ -61,11 +61,11 @@ var Keys = Keymaps{ ), InsertItem: key.NewBinding( key.WithKeys("a"), - key.WithHelp("a", "add item"), + key.WithHelp("a", "add"), ), EditItem: key.NewBinding( key.WithKeys("e"), - key.WithHelp("e", "edit item"), + key.WithHelp("e", "edit"), ), Choose: key.NewBinding( key.WithKeys("enter"), diff --git a/internal/tui/styles/collections-styles.go b/internal/tui/styles/collections-styles.go index 600e885..f044adb 100644 --- a/internal/tui/styles/collections-styles.go +++ b/internal/tui/styles/collections-styles.go @@ -3,6 +3,7 @@ package styles import "github.com/charmbracelet/lipgloss" var ( - SelectedListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) - InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) + SelectedListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) + UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) + InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) ) diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go index 0e856aa..1ca21ba 100644 --- a/internal/tui/views/endpoints-view-helper.go +++ b/internal/tui/views/endpoints-view-helper.go @@ -9,18 +9,22 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/tui/components/OptionsProvider" + "github.com/maniac-en/req/internal/tui/styles" ) func createEndpointsDelegate() list.ItemDelegate { d := epItemDelegate{} - // d.Styles.SelectedTitle = styles.SelectedListStyle - // d.Styles.SelectedDesc = styles.SelectedListStyle + d.SelectedTitleStyle = styles.SelectedListStyle + d.UnselectedTitleStyle = styles.UnselectedListStyle return d } -type epItemDelegate struct{} +type epItemDelegate struct { + SelectedTitleStyle lipgloss.Style + UnselectedTitleStyle lipgloss.Style +} func (e epItemDelegate) Height() int { return 1 } func (e epItemDelegate) Spacing() int { return 1 } @@ -34,10 +38,10 @@ func (e epItemDelegate) Render(w io.Writer, m list.Model, index int, listItem li str := fmt.Sprintf("%s %s", item.Subtext, item.Name) - fn := lipgloss.NewStyle().Render + fn := e.UnselectedTitleStyle.Render if index == m.Index() { fn = func(s ...string) string { - return lipgloss.NewStyle().Render("> " + strings.Join(s, " ")) + return e.SelectedTitleStyle.Render(strings.Join(s, " ")) } } diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 74ab07b..9e7ee4a 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -37,7 +37,7 @@ func (e *EndpointsView) Help() []key.Binding { } func (e *EndpointsView) GetFooterSegment() string { - return fmt.Sprintf("%s/", e.collection.Name) + return fmt.Sprintf("%s/%s", e.collection.Name, e.list.GetSelected().Name) } func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { From ff6e695c6854c93cab9704b9901394c39a431ffc Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 7 Nov 2025 18:38:35 +0100 Subject: [PATCH 04/46] initing the method picker --- .../tui/components/MethodPicker/component.go | 65 ++++++++++++++++ internal/tui/views/endpoints-view.go | 33 +++++++-- internal/tui/views/request-view.go | 74 +++++++++++++++++++ 3 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 internal/tui/components/MethodPicker/component.go create mode 100644 internal/tui/views/request-view.go diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go new file mode 100644 index 0000000..805f91b --- /dev/null +++ b/internal/tui/components/MethodPicker/component.go @@ -0,0 +1,65 @@ +package methodpicker + +import ( + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/tui/keybinds" +) + +type MethodPicker[T any] struct { + list list.Model + onSelectAction tea.Msg + keys *keybinds.ListKeyMap + width int + height int + itemMapper func([]T) []list.Item +} + +type MethodOption struct { + Name string + Type string +} + +func (m MethodOption) Title() string { return m.Name } +func (m MethodOption) Description() string { return m.Name } +func (m MethodOption) Value() string { return m.Type } +func (m MethodOption) FilterValue() string { return "" } + +func (m MethodPicker[T]) Init() tea.Cmd { + return nil +} + +func (m MethodPicker[T]) Update(msg tea.Msg) (MethodPicker[T], tea.Cmd) { + return m, nil +} + +func (m MethodPicker[T]) View() string { + return "hello" +} + +func (m *MethodPicker[T]) OnFocus() { +} + +func (m MethodPicker[T]) OnBlur() { + +} + +func (m MethodPicker[T]) GetSelected() MethodOption { + return m.list.SelectedItem().(MethodOption) +} + +func (m *MethodPicker[T]) Help() []key.Binding { + return []key.Binding{ + m.keys.NextPage, + m.keys.PrevPage, + m.keys.CursorUp, + m.keys.CursorDown, + m.keys.Accept, + } +} + +func NewMethodPicker[T any]() MethodPicker[T] { + + return MethodPicker[T]{} +} diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 9e7ee4a..ba1cd87 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -8,6 +8,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/database" "github.com/maniac-en/req/internal/backend/endpoints" optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" @@ -15,13 +16,22 @@ import ( "github.com/maniac-en/req/internal/tui/messages" ) +type epFocused string + +const ( + listView = "listview" + requestView = "requestview" +) + type EndpointsView struct { - height int - collection optionsProvider.Option - width int - order int - list optionsProvider.OptionsProvider[endpoints.EndpointEntity, database.Endpoint] - manager *endpoints.EndpointsManager + height int + focused epFocused + collection optionsProvider.Option + width int + order int + list optionsProvider.OptionsProvider[endpoints.EndpointEntity, database.Endpoint] + requestView ViewInterface + manager *endpoints.EndpointsManager } func (e *EndpointsView) Init() tea.Cmd { @@ -61,15 +71,20 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.manager.Delete(context.Background(), msg.ItemID) e.list.RefreshItems() } + switch e.focused { + case listView: + e.list, cmd = e.list.Update(msg) + case requestView: + e.requestView, cmd = e.requestView.Update(msg) + } - e.list, cmd = e.list.Update(msg) cmds = append(cmds, cmd) return e, tea.Batch(cmds...) } func (e *EndpointsView) View() string { - return e.list.View() + return lipgloss.JoinHorizontal(lipgloss.Center, e.list.View(), e.requestView.View()) } func (e *EndpointsView) OnFocus() { @@ -135,6 +150,8 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin config.Source = "endpoints" view.list = optionsProvider.NewOptionsProvider(config) + view.requestView = NewRequestView() + view.focused = listView return view } diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go new file mode 100644 index 0000000..f9f2ec9 --- /dev/null +++ b/internal/tui/views/request-view.go @@ -0,0 +1,74 @@ +package views + +import ( + "errors" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/backend/database" + methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" + "github.com/maniac-en/req/internal/tui/keybinds" +) + +type RequestView struct { + width int + height int + help help.Model + keys *keybinds.ListKeyMap + methodPicker methodpicker.MethodPicker[string] + order int + endpoint database.Endpoint +} + +func (r RequestView) Init() tea.Cmd { + return nil +} + +func (r RequestView) Name() string { + return "Request" +} + +func (r RequestView) Help() []key.Binding { + return []key.Binding{} +} + +func (r RequestView) GetFooterSegment() string { + return "" +} + +func (r RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { + var cmd tea.Cmd + + return r, cmd +} + +func (r RequestView) View() string { + return r.methodPicker.View() +} + +func (r RequestView) SetState(items ...any) error { + if len(items) != 1 { + return errors.New("Incorrect amount of fields supplied") + } + r.SetState(items[0]) + return nil +} + +func (r RequestView) Order() int { + return r.order +} + +func (r RequestView) OnFocus() { + +} + +func (r RequestView) OnBlur() { + +} + +func NewRequestView() *RequestView { + return &RequestView{ + methodPicker: methodpicker.NewMethodPicker[string](), + } +} From 139889e1c73dcdb022c4988f558a3db6d352548c Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Sat, 8 Nov 2025 17:39:27 +0100 Subject: [PATCH 05/46] basics for the method picker implemented --- .../tui/components/MethodPicker/component.go | 30 ++++++++++-- .../tui/components/MethodPicker/config.go | 27 +++++++++++ internal/tui/styles/request-styles.go | 12 +++++ internal/tui/views/endpoints-view.go | 4 +- internal/tui/views/request-view.go | 48 +++++++++++++++++-- 5 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 internal/tui/components/MethodPicker/config.go create mode 100644 internal/tui/styles/request-styles.go diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go index 805f91b..506e6b3 100644 --- a/internal/tui/components/MethodPicker/component.go +++ b/internal/tui/components/MethodPicker/component.go @@ -24,18 +24,22 @@ type MethodOption struct { func (m MethodOption) Title() string { return m.Name } func (m MethodOption) Description() string { return m.Name } func (m MethodOption) Value() string { return m.Type } -func (m MethodOption) FilterValue() string { return "" } +func (m MethodOption) FilterValue() string { return m.Name } func (m MethodPicker[T]) Init() tea.Cmd { return nil } func (m MethodPicker[T]) Update(msg tea.Msg) (MethodPicker[T], tea.Cmd) { - return m, nil + var cmd tea.Cmd + + m.list, cmd = m.list.Update(msg) + + return m, cmd } func (m MethodPicker[T]) View() string { - return "hello" + return m.list.View() } func (m *MethodPicker[T]) OnFocus() { @@ -59,7 +63,23 @@ func (m *MethodPicker[T]) Help() []key.Binding { } } -func NewMethodPicker[T any]() MethodPicker[T] { +func NewMethodPicker[T any](config MethodPickerConfig[T]) MethodPicker[T] { + method := MethodPicker[T]{} + + picker := list.New( + config.ItemMapper(config.Items), + config.Delegate, + config.Width, + config.Height, + ) + + picker.SetFilteringEnabled(config.FilteringEnabled) + picker.SetShowStatusBar(config.ShowStatusBar) + picker.SetShowPagination(config.ShowPagination) + picker.SetShowHelp(config.ShowHelp) + picker.SetShowTitle(config.ShowTitle) + + method.list = picker - return MethodPicker[T]{} + return method } diff --git a/internal/tui/components/MethodPicker/config.go b/internal/tui/components/MethodPicker/config.go new file mode 100644 index 0000000..689a6d5 --- /dev/null +++ b/internal/tui/components/MethodPicker/config.go @@ -0,0 +1,27 @@ +package methodpicker + +import ( + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/tui/keybinds" +) + +type MethodPickerConfig[T any] struct { + Items []T + ItemMapper func([]T) []list.Item + FilteringEnabled bool + + Delegate list.ItemDelegate + KeyMap list.KeyMap + AdditionalKeymaps *keybinds.ListKeyMap + + OnSelectAction tea.Msg + + ShowPagination bool + ShowStatusBar bool + ShowHelp bool + ShowTitle bool + Width, Height int +} + +func config() {} diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go new file mode 100644 index 0000000..c528a1c --- /dev/null +++ b/internal/tui/styles/request-styles.go @@ -0,0 +1,12 @@ +package styles + +import "github.com/charmbracelet/lipgloss" + +func RequestLayout(height, width int) func(...string) string { + return lipgloss.NewStyle(). + Height(height). + Width(width). + Padding(1, 0, 0, 1). + BorderLeft(true). + Render +} diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index ba1cd87..c90f351 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -70,6 +70,8 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case messages.DeleteItem: e.manager.Delete(context.Background(), msg.ItemID) e.list.RefreshItems() + case messages.ChooseItem[optionsProvider.Option]: + e.focused = requestView } switch e.focused { case listView: @@ -84,7 +86,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { } func (e *EndpointsView) View() string { - return lipgloss.JoinHorizontal(lipgloss.Center, e.list.View(), e.requestView.View()) + return lipgloss.JoinHorizontal(lipgloss.Top, e.list.View(), e.requestView.View()) } func (e *EndpointsView) OnFocus() { diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index f9f2ec9..68c84b4 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -5,10 +5,12 @@ import ( "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/maniac-en/req/internal/backend/database" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" "github.com/maniac-en/req/internal/tui/keybinds" + "github.com/maniac-en/req/internal/tui/styles" ) type RequestView struct { @@ -39,12 +41,21 @@ func (r RequestView) GetFooterSegment() string { func (r RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { var cmd tea.Cmd + var cmds []tea.Cmd + switch msg := msg.(type) { + case tea.WindowSizeMsg: + r.height = msg.Height + r.width = msg.Width + } + + r.methodPicker, cmd = r.methodPicker.Update(msg) + cmds = append(cmds, cmd) - return r, cmd + return r, tea.Batch(cmds...) } func (r RequestView) View() string { - return r.methodPicker.View() + return styles.RequestLayout(10, 100)(r.methodPicker.View()) } func (r RequestView) SetState(items ...any) error { @@ -67,8 +78,39 @@ func (r RequestView) OnBlur() { } +func methodItemMapper(items []string) []list.Item { + listItems := make([]list.Item, len(items)) + for index, item := range items { + listItems[index] = methodpicker.MethodOption{ + Name: item, + Type: item, + } + } + return listItems +} + func NewRequestView() *RequestView { + methods := []string{ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + } + + config := methodpicker.MethodPickerConfig[string]{ + Items: methods, + ItemMapper: methodItemMapper, + FilteringEnabled: false, + ShowPagination: false, + ShowStatusBar: false, + Delegate: list.DefaultDelegate{}, + ShowHelp: false, + ShowTitle: false, + Width: 30, + Height: 1, + } return &RequestView{ - methodPicker: methodpicker.NewMethodPicker[string](), + methodPicker: methodpicker.NewMethodPicker[string](config), } } From 3af4b5755bfbacc8253f4f63bc52fd918b0fd755 Mon Sep 17 00:00:00 2001 From: Yash Ranjan Date: Wed, 25 Feb 2026 15:04:28 +0100 Subject: [PATCH 06/46] fixed some navigation issues --- internal/tui/app/model.go | 15 ++---- .../tui/components/MethodPicker/component.go | 9 ++-- .../tui/components/MethodPicker/config.go | 2 +- .../components/OptionsProvider/component.go | 23 ++++---- .../tui/components/OptionsProvider/config.go | 2 +- internal/tui/messages/messages.go | 1 + internal/tui/styles/collections-styles.go | 7 +-- internal/tui/styles/request-styles.go | 4 ++ internal/tui/views/collections-view-helper.go | 14 +++-- internal/tui/views/endpoints-view-helper.go | 8 ++- internal/tui/views/endpoints-view.go | 26 +++++++++ internal/tui/views/request-view-helper.go | 53 +++++++++++++++++++ internal/tui/views/request-view.go | 40 +++++++++----- internal/tui/views/view-reference.go | 8 +++ main.go | 1 + 15 files changed, 164 insertions(+), 49 deletions(-) create mode 100644 internal/tui/views/request-view-helper.go create mode 100644 internal/tui/views/view-reference.go diff --git a/internal/tui/app/model.go b/internal/tui/app/model.go index 48c54e2..07e8f8f 100644 --- a/internal/tui/app/model.go +++ b/internal/tui/app/model.go @@ -67,16 +67,18 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } case messages.NavigateToView: - a.Views[a.focusedView].OnBlur() - if msg.Data != nil { err := a.Views[ViewName(msg.ViewName)].SetState(msg.Data) if err != nil { log.Error("failed to set view state during navigation", "target_view", msg.ViewName, "error", err) return a, nil } + } else if msg.Target != views.MainModel { + break } + a.Views[a.focusedView].OnBlur() + a.focusedView = ViewName(msg.ViewName) a.Views[a.focusedView].OnFocus() return a, nil @@ -89,15 +91,6 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch { case key.Matches(msg, keybinds.Keys.Quit): return a, tea.Quit - case key.Matches(msg, keybinds.Keys.Back): - if a.focusedView == Endpoints { - return a, func() tea.Msg { - return messages.NavigateToView{ - ViewName: string(Collections), - Data: nil, - } - } - } } } diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go index 506e6b3..46718b7 100644 --- a/internal/tui/components/MethodPicker/component.go +++ b/internal/tui/components/MethodPicker/component.go @@ -10,6 +10,7 @@ import ( type MethodPicker[T any] struct { list list.Model onSelectAction tea.Msg + delegateGenner func(bool) list.ItemDelegate keys *keybinds.ListKeyMap width int height int @@ -43,10 +44,11 @@ func (m MethodPicker[T]) View() string { } func (m *MethodPicker[T]) OnFocus() { + m.list.SetDelegate(m.delegateGenner(true)) } -func (m MethodPicker[T]) OnBlur() { - +func (m *MethodPicker[T]) OnBlur() { + m.list.SetDelegate(m.delegateGenner(false)) } func (m MethodPicker[T]) GetSelected() MethodOption { @@ -68,7 +70,7 @@ func NewMethodPicker[T any](config MethodPickerConfig[T]) MethodPicker[T] { picker := list.New( config.ItemMapper(config.Items), - config.Delegate, + config.Delegate(false), config.Width, config.Height, ) @@ -80,6 +82,7 @@ func NewMethodPicker[T any](config MethodPickerConfig[T]) MethodPicker[T] { picker.SetShowTitle(config.ShowTitle) method.list = picker + method.delegateGenner = config.Delegate return method } diff --git a/internal/tui/components/MethodPicker/config.go b/internal/tui/components/MethodPicker/config.go index 689a6d5..5ed7ccc 100644 --- a/internal/tui/components/MethodPicker/config.go +++ b/internal/tui/components/MethodPicker/config.go @@ -11,7 +11,7 @@ type MethodPickerConfig[T any] struct { ItemMapper func([]T) []list.Item FilteringEnabled bool - Delegate list.ItemDelegate + Delegate func(bool) list.ItemDelegate KeyMap list.KeyMap AdditionalKeymaps *keybinds.ListKeyMap diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index 2bf2aa0..9fa564c 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -23,6 +23,7 @@ type OptionsProvider[T, U any] struct { list list.Model input OptionsInput onSelectAction tea.Msg + delegateGenner func(bool) list.ItemDelegate keys *keybinds.ListKeyMap width int height int @@ -111,10 +112,11 @@ func (o OptionsProvider[T, U]) View() string { } func (o *OptionsProvider[T, U]) OnFocus() { + o.list.SetDelegate(o.delegateGenner(true)) } -func (o OptionsProvider[T, U]) OnBlur() { - +func (o *OptionsProvider[T, U]) OnBlur() { + o.list.SetDelegate(o.delegateGenner(false)) } func (o OptionsProvider[T, U]) GetSelected() Option { @@ -181,7 +183,7 @@ func initList[T, U any](config *ListConfig[T, U]) list.Model { items := config.ItemMapper(rawItems) - list := list.New(items, config.Delegate, 30, 30) + list := list.New(items, config.Delegate(true), 30, 30) // list configuration list.SetFilteringEnabled(config.FilteringEnabled) @@ -216,12 +218,13 @@ func NewOptionsProvider[T, U any](config *ListConfig[T, U]) OptionsProvider[T, U } return OptionsProvider[T, U]{ - list: initList(config), - focused: listComponent, - input: NewOptionsInput(&inputConfig), - getItems: config.GetItemsFunc, - itemMapper: config.ItemMapper, - keys: config.AdditionalKeymaps, - source: config.Source, + list: initList(config), + focused: listComponent, + input: NewOptionsInput(&inputConfig), + getItems: config.GetItemsFunc, + itemMapper: config.ItemMapper, + keys: config.AdditionalKeymaps, + source: config.Source, + delegateGenner: config.Delegate, } } diff --git a/internal/tui/components/OptionsProvider/config.go b/internal/tui/components/OptionsProvider/config.go index 552681a..9ae7f4b 100644 --- a/internal/tui/components/OptionsProvider/config.go +++ b/internal/tui/components/OptionsProvider/config.go @@ -21,7 +21,7 @@ type ListConfig[T, U any] struct { FilteringEnabled bool - Delegate list.ItemDelegate + Delegate func(bool) list.ItemDelegate KeyMap list.KeyMap AdditionalKeymaps *keybinds.ListKeyMap diff --git a/internal/tui/messages/messages.go b/internal/tui/messages/messages.go index a3192b6..21ad3b9 100644 --- a/internal/tui/messages/messages.go +++ b/internal/tui/messages/messages.go @@ -21,6 +21,7 @@ type DeactivateView struct{} type NavigateToView struct { ViewName string + Target string Data interface{} } diff --git a/internal/tui/styles/collections-styles.go b/internal/tui/styles/collections-styles.go index f044adb..b4d4393 100644 --- a/internal/tui/styles/collections-styles.go +++ b/internal/tui/styles/collections-styles.go @@ -3,7 +3,8 @@ package styles import "github.com/charmbracelet/lipgloss" var ( - SelectedListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) - UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) - InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) + SelectedInactiveListStyle = lipgloss.NewStyle().PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true) + SelectedActiveListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) + UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) + InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) ) diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index c528a1c..2c0b491 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -10,3 +10,7 @@ func RequestLayout(height, width int) func(...string) string { BorderLeft(true). Render } + +var ( + ActiveRequestItem = lipgloss.NewStyle() +) diff --git a/internal/tui/views/collections-view-helper.go b/internal/tui/views/collections-view-helper.go index f197ad7..9c7b4e7 100644 --- a/internal/tui/views/collections-view-helper.go +++ b/internal/tui/views/collections-view-helper.go @@ -7,23 +7,27 @@ import ( "github.com/maniac-en/req/internal/tui/styles" ) -func createCollectionDelegate() list.ItemDelegate { +func createCollectionDelegate(active bool) list.ItemDelegate { d := list.NewDefaultDelegate() + if active { + d.Styles.SelectedTitle = styles.SelectedActiveListStyle + } else { + d.Styles.SelectedTitle = styles.SelectedInactiveListStyle - d.Styles.SelectedTitle = styles.SelectedListStyle - d.Styles.SelectedDesc = styles.SelectedListStyle + } + d.Styles.SelectedDesc = styles.UnselectedListStyle return d } -func defaultListConfig[T, U any](binds *keybinds.ListKeyMap, delegateCreator func() list.ItemDelegate) *optionsProvider.ListConfig[T, U] { +func defaultListConfig[T, U any](binds *keybinds.ListKeyMap, delegateCreator func(bool) list.ItemDelegate) *optionsProvider.ListConfig[T, U] { config := optionsProvider.ListConfig[T, U]{ ShowPagination: true, ShowStatusBar: false, ShowHelp: false, ShowTitle: false, FilteringEnabled: true, - Delegate: delegateCreator(), + Delegate: delegateCreator, KeyMap: list.KeyMap{ CursorUp: binds.CursorUp, CursorDown: binds.CursorDown, diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go index 1ca21ba..7a4e659 100644 --- a/internal/tui/views/endpoints-view-helper.go +++ b/internal/tui/views/endpoints-view-helper.go @@ -12,10 +12,14 @@ import ( "github.com/maniac-en/req/internal/tui/styles" ) -func createEndpointsDelegate() list.ItemDelegate { +func createEndpointsDelegate(active bool) list.ItemDelegate { d := epItemDelegate{} - d.SelectedTitleStyle = styles.SelectedListStyle + if active { + d.SelectedTitleStyle = styles.SelectedActiveListStyle + } else { + d.SelectedTitleStyle = styles.SelectedInactiveListStyle + } d.UnselectedTitleStyle = styles.UnselectedListStyle return d diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index c90f351..045f78d 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -71,7 +71,33 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.manager.Delete(context.Background(), msg.ItemID) e.list.RefreshItems() case messages.ChooseItem[optionsProvider.Option]: + e.list.OnBlur() + e.requestView.OnFocus() e.focused = requestView + case messages.NavigateToView: + if msg.Target != Endpoints { + break + } + e.requestView.OnBlur() + e.focused = listView + e.list.OnFocus() + + case tea.KeyMsg: + switch { + case key.Matches(msg, keybinds.Keys.Back): + navMsg := messages.NavigateToView{ + ViewName: Collections, + Target: MainModel, + } + switch e.focused { + case listView: + return e, func() tea.Msg { + return navMsg + } + case requestView: + e.requestView, cmd = e.requestView.Update(msg) + } + } } switch e.focused { case listView: diff --git a/internal/tui/views/request-view-helper.go b/internal/tui/views/request-view-helper.go new file mode 100644 index 0000000..38a30db --- /dev/null +++ b/internal/tui/views/request-view-helper.go @@ -0,0 +1,53 @@ +package views + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" + "github.com/maniac-en/req/internal/tui/styles" +) + +func createRequestDelegate(active bool) list.ItemDelegate { + d := requestDelegate{} + + if active { + d.SelectedTitleStyle = styles.SelectedActiveListStyle + } else { + d.SelectedTitleStyle = styles.SelectedInactiveListStyle + } + d.UnselectedTitleStyle = styles.UnselectedListStyle + + return d +} + +type requestDelegate struct { + SelectedTitleStyle lipgloss.Style + UnselectedTitleStyle lipgloss.Style +} + +func (r requestDelegate) Height() int { return 1 } +func (r requestDelegate) Spacing() int { return 1 } +func (r requestDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } +func (r requestDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + item, ok := listItem.(methodpicker.MethodOption) + + if !ok { + return + } + + str := fmt.Sprintf("< %s >", item.Name) + + fn := r.UnselectedTitleStyle.Render + if index == m.Index() { + fn = func(s ...string) string { + return r.SelectedTitleStyle.Render(strings.Join(s, " ")) + } + } + + fmt.Fprint(w, fn(str)) +} diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 68c84b4..f40ea31 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -8,8 +8,10 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/maniac-en/req/internal/backend/database" + "github.com/maniac-en/req/internal/log" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" "github.com/maniac-en/req/internal/tui/keybinds" + "github.com/maniac-en/req/internal/tui/messages" "github.com/maniac-en/req/internal/tui/styles" ) @@ -23,29 +25,41 @@ type RequestView struct { endpoint database.Endpoint } -func (r RequestView) Init() tea.Cmd { +func (r *RequestView) Init() tea.Cmd { return nil } -func (r RequestView) Name() string { +func (r *RequestView) Name() string { return "Request" } -func (r RequestView) Help() []key.Binding { +func (r *RequestView) Help() []key.Binding { return []key.Binding{} } -func (r RequestView) GetFooterSegment() string { +func (r *RequestView) GetFooterSegment() string { return "" } -func (r RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { +func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { var cmd tea.Cmd var cmds []tea.Cmd switch msg := msg.(type) { case tea.WindowSizeMsg: r.height = msg.Height r.width = msg.Width + case tea.KeyMsg: + log.Info("received a key") + switch { + case key.Matches(msg, keybinds.Keys.Back): + log.Info("changing to ep") + return r, func() tea.Msg { + return messages.NavigateToView{ + ViewName: Endpoints, + Target: Endpoints, + } + } + } } r.methodPicker, cmd = r.methodPicker.Update(msg) @@ -54,11 +68,11 @@ func (r RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return r, tea.Batch(cmds...) } -func (r RequestView) View() string { +func (r *RequestView) View() string { return styles.RequestLayout(10, 100)(r.methodPicker.View()) } -func (r RequestView) SetState(items ...any) error { +func (r *RequestView) SetState(items ...any) error { if len(items) != 1 { return errors.New("Incorrect amount of fields supplied") } @@ -66,16 +80,16 @@ func (r RequestView) SetState(items ...any) error { return nil } -func (r RequestView) Order() int { +func (r *RequestView) Order() int { return r.order } -func (r RequestView) OnFocus() { - +func (r *RequestView) OnFocus() { + r.methodPicker.OnFocus() } -func (r RequestView) OnBlur() { - +func (r *RequestView) OnBlur() { + r.methodPicker.OnBlur() } func methodItemMapper(items []string) []list.Item { @@ -104,7 +118,7 @@ func NewRequestView() *RequestView { FilteringEnabled: false, ShowPagination: false, ShowStatusBar: false, - Delegate: list.DefaultDelegate{}, + Delegate: createRequestDelegate, ShowHelp: false, ShowTitle: false, Width: 30, diff --git a/internal/tui/views/view-reference.go b/internal/tui/views/view-reference.go new file mode 100644 index 0000000..0457768 --- /dev/null +++ b/internal/tui/views/view-reference.go @@ -0,0 +1,8 @@ +package views + +const ( + Collections = "collections" + Endpoints = "endpoints" + Request = "request" + MainModel = "main" +) diff --git a/main.go b/main.go index ff39d30..43de018 100644 --- a/main.go +++ b/main.go @@ -70,6 +70,7 @@ func initPaths() error { } DBPATH = filepath.Join(APPDIR, "app.db") LOGPATH = filepath.Join(APPDIR, "req.log") + return nil } From 041c667f44cad5910f92a78b5db40fc44e4782ed Mon Sep 17 00:00:00 2001 From: Yash Ranjan Date: Wed, 25 Feb 2026 23:17:54 +0100 Subject: [PATCH 07/46] url input created --- .../tui/components/ComponentTypes/types.go | 16 ++++ .../tui/components/MethodPicker/component.go | 22 ++--- .../tui/components/MethodPicker/config.go | 2 +- .../components/OptionsProvider/component.go | 4 + internal/tui/components/UrlInput/component.go | 61 +++++++++++++ internal/tui/components/UrlInput/config.go | 9 ++ internal/tui/keybinds/keys.go | 5 ++ internal/tui/styles/request-styles.go | 22 ++++- internal/tui/views/endpoints-view.go | 11 ++- internal/tui/views/request-view-helper.go | 42 ++++++++- internal/tui/views/request-view.go | 90 +++++++++++-------- 11 files changed, 230 insertions(+), 54 deletions(-) create mode 100644 internal/tui/components/ComponentTypes/types.go create mode 100644 internal/tui/components/UrlInput/component.go create mode 100644 internal/tui/components/UrlInput/config.go diff --git a/internal/tui/components/ComponentTypes/types.go b/internal/tui/components/ComponentTypes/types.go new file mode 100644 index 0000000..3e5ffcd --- /dev/null +++ b/internal/tui/components/ComponentTypes/types.go @@ -0,0 +1,16 @@ +package componenttypes + +import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" +) + +type ComponentInterface interface { + Init() tea.Cmd + Update(tea.Msg) (ComponentInterface, tea.Cmd) + View() string + SetSize(int, int) + Help() []key.Binding + OnFocus() + OnBlur() +} diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go index 46718b7..f954b4b 100644 --- a/internal/tui/components/MethodPicker/component.go +++ b/internal/tui/components/MethodPicker/component.go @@ -4,6 +4,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" "github.com/maniac-en/req/internal/tui/keybinds" ) @@ -27,11 +28,14 @@ func (m MethodOption) Description() string { return m.Name } func (m MethodOption) Value() string { return m.Type } func (m MethodOption) FilterValue() string { return m.Name } -func (m MethodPicker[T]) Init() tea.Cmd { +func (m *MethodPicker[T]) Init() tea.Cmd { return nil } -func (m MethodPicker[T]) Update(msg tea.Msg) (MethodPicker[T], tea.Cmd) { +func (m *MethodPicker[T]) SetSize(width, height int) { +} + +func (m *MethodPicker[T]) Update(msg tea.Msg) (componenttypes.ComponentInterface, tea.Cmd) { var cmd tea.Cmd m.list, cmd = m.list.Update(msg) @@ -39,7 +43,7 @@ func (m MethodPicker[T]) Update(msg tea.Msg) (MethodPicker[T], tea.Cmd) { return m, cmd } -func (m MethodPicker[T]) View() string { +func (m *MethodPicker[T]) View() string { return m.list.View() } @@ -51,22 +55,19 @@ func (m *MethodPicker[T]) OnBlur() { m.list.SetDelegate(m.delegateGenner(false)) } -func (m MethodPicker[T]) GetSelected() MethodOption { +func (m *MethodPicker[T]) GetSelected() MethodOption { return m.list.SelectedItem().(MethodOption) } func (m *MethodPicker[T]) Help() []key.Binding { return []key.Binding{ - m.keys.NextPage, - m.keys.PrevPage, - m.keys.CursorUp, m.keys.CursorDown, - m.keys.Accept, + m.keys.CursorUp, } } -func NewMethodPicker[T any](config MethodPickerConfig[T]) MethodPicker[T] { - method := MethodPicker[T]{} +func NewMethodPicker[T any](config MethodPickerConfig[T]) *MethodPicker[T] { + method := &MethodPicker[T]{} picker := list.New( config.ItemMapper(config.Items), @@ -82,6 +83,7 @@ func NewMethodPicker[T any](config MethodPickerConfig[T]) MethodPicker[T] { picker.SetShowTitle(config.ShowTitle) method.list = picker + method.keys = config.KeyMap method.delegateGenner = config.Delegate return method diff --git a/internal/tui/components/MethodPicker/config.go b/internal/tui/components/MethodPicker/config.go index 5ed7ccc..2408bc6 100644 --- a/internal/tui/components/MethodPicker/config.go +++ b/internal/tui/components/MethodPicker/config.go @@ -12,7 +12,7 @@ type MethodPickerConfig[T any] struct { FilteringEnabled bool Delegate func(bool) list.ItemDelegate - KeyMap list.KeyMap + KeyMap *keybinds.ListKeyMap AdditionalKeymaps *keybinds.ListKeyMap OnSelectAction tea.Msg diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index 9fa564c..fc03a7b 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -143,6 +143,10 @@ func (o *OptionsProvider[T, U]) RefreshItems() { o.list.SetItems(o.itemMapper(newItems)) } +func (o *OptionsProvider[T, U]) GetSize() (int, int) { + return o.width, o.height +} + func (o *OptionsProvider[T, U]) Help() []key.Binding { var binds []key.Binding switch o.focused { diff --git a/internal/tui/components/UrlInput/component.go b/internal/tui/components/UrlInput/component.go new file mode 100644 index 0000000..23d5ad2 --- /dev/null +++ b/internal/tui/components/UrlInput/component.go @@ -0,0 +1,61 @@ +package urlinput + +import ( + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" +) + +type UrlInput struct { + text textinput.Model + styleGenner func(bool) lipgloss.Style + width int + height int + active bool +} + +func (u *UrlInput) Init() tea.Cmd { + return nil +} + +func (u *UrlInput) SetSize(width, height int) { + u.text.PromptStyle.Height(height) + u.text.PromptStyle.Width(width) +} + +func (u *UrlInput) Help() []key.Binding { + return []key.Binding{} +} + +func (u *UrlInput) Update(msg tea.Msg) (componenttypes.ComponentInterface, tea.Cmd) { + var cmd tea.Cmd + + u.text, cmd = u.text.Update(msg) + + return u, cmd +} + +func (u *UrlInput) View() string { + return u.styleGenner(u.active).Render(u.text.View()) +} + +func (u *UrlInput) OnFocus() { + u.text.Focus() + u.active = true +} + +func (u *UrlInput) OnBlur() { + u.active = false +} + +func NewUrlInput(config UrlInputConfig) *UrlInput { + return &UrlInput{ + text: textinput.New(), + width: config.Width, + height: config.Height, + styleGenner: config.StyleGenner, + active: false, + } +} diff --git a/internal/tui/components/UrlInput/config.go b/internal/tui/components/UrlInput/config.go new file mode 100644 index 0000000..7e5b16a --- /dev/null +++ b/internal/tui/components/UrlInput/config.go @@ -0,0 +1,9 @@ +package urlinput + +import "github.com/charmbracelet/lipgloss" + +type UrlInputConfig struct { + StyleGenner func(bool) lipgloss.Style + Width int + Height int +} diff --git a/internal/tui/keybinds/keys.go b/internal/tui/keybinds/keys.go index f731a98..99f5b08 100644 --- a/internal/tui/keybinds/keys.go +++ b/internal/tui/keybinds/keys.go @@ -14,6 +14,7 @@ type Keymaps struct { Up key.Binding Down key.Binding NextPage key.Binding + Next key.Binding PrevPage key.Binding Filter key.Binding ClearFilter key.Binding @@ -31,6 +32,10 @@ var Keys = Keymaps{ key.WithKeys("up", "k"), key.WithHelp("↑/k", "back"), ), + Next: key.NewBinding( + key.WithKeys("tab"), + key.WithHelp("tab", "next field"), + ), Down: key.NewBinding( key.WithKeys("down", "j"), key.WithHelp("↓/j", "down"), diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index 2c0b491..9819472 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -6,11 +6,27 @@ func RequestLayout(height, width int) func(...string) string { return lipgloss.NewStyle(). Height(height). Width(width). - Padding(1, 0, 0, 1). - BorderLeft(true). + Margin(0, 0, 0, 5). + Padding(1, 0, 0, 10). + Border(lipgloss.NormalBorder(), false, false, false, true). Render } +func UrlInputStyle(active bool) lipgloss.Style { + var color lipgloss.Color + + if active { + color = accent + } + + return lipgloss.NewStyle(). + Foreground(color). + Margin(0, 0, 0, 5). + Border(lipgloss.NormalBorder(), false, false, true, false). + BorderForeground(color) +} + var ( - ActiveRequestItem = lipgloss.NewStyle() + ActiveRequestItem = lipgloss.NewStyle().Foreground(accent) + InactiveRequestItem = lipgloss.NewStyle() ) diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 045f78d..ee1d460 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -43,7 +43,11 @@ func (e *EndpointsView) Name() string { } func (e *EndpointsView) Help() []key.Binding { - return e.list.Help() + if e.focused == listView { + return e.list.Help() + } else { + return e.requestView.Help() + } } func (e *EndpointsView) GetFooterSegment() string { @@ -58,6 +62,11 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.height = msg.Height e.width = msg.Width e.list, cmd = e.list.Update(msg) + w, _ := e.list.GetSize() + e.requestView, cmd = e.requestView.Update(tea.WindowSizeMsg{ + Height: msg.Height, + Width: msg.Width - w, + }) cmds = append(cmds, cmd) case messages.ItemAdded: e.manager.CreateEndpoint(context.Background(), endpoints.EndpointData{ diff --git a/internal/tui/views/request-view-helper.go b/internal/tui/views/request-view-helper.go index 38a30db..b935c15 100644 --- a/internal/tui/views/request-view-helper.go +++ b/internal/tui/views/request-view-helper.go @@ -9,6 +9,8 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" + urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" + "github.com/maniac-en/req/internal/tui/keybinds" "github.com/maniac-en/req/internal/tui/styles" ) @@ -16,11 +18,11 @@ func createRequestDelegate(active bool) list.ItemDelegate { d := requestDelegate{} if active { - d.SelectedTitleStyle = styles.SelectedActiveListStyle + d.SelectedTitleStyle = styles.ActiveRequestItem } else { - d.SelectedTitleStyle = styles.SelectedInactiveListStyle + d.SelectedTitleStyle = styles.InactiveRequestItem } - d.UnselectedTitleStyle = styles.UnselectedListStyle + d.UnselectedTitleStyle = styles.InactiveRequestItem return d } @@ -51,3 +53,37 @@ func (r requestDelegate) Render(w io.Writer, m list.Model, index int, listItem l fmt.Fprint(w, fn(str)) } + +func createMethodPickerConfig() methodpicker.MethodPickerConfig[string] { + methods := []string{ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + } + + keys := keybinds.NewListKeyMap() + + return methodpicker.MethodPickerConfig[string]{ + Items: methods, + ItemMapper: methodItemMapper, + FilteringEnabled: false, + ShowPagination: false, + ShowStatusBar: false, + KeyMap: keys, + Delegate: createRequestDelegate, + ShowHelp: false, + ShowTitle: false, + Width: 30, + Height: 1, + } +} + +func createURLInputConfig() urlinput.UrlInputConfig { + return urlinput.UrlInputConfig{ + Width: 50, + Height: 1, + StyleGenner: styles.UrlInputStyle, + } +} diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index f40ea31..5416558 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -7,22 +7,38 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/database" - "github.com/maniac-en/req/internal/log" + componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" + urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" "github.com/maniac-en/req/internal/tui/keybinds" "github.com/maniac-en/req/internal/tui/messages" "github.com/maniac-en/req/internal/tui/styles" ) +type reqFocused string + +const ( + methodPicker = "method" + urlInput = "input" +) + +var componentList = []reqFocused{ + methodPicker, + urlInput, +} + type RequestView struct { - width int - height int - help help.Model - keys *keybinds.ListKeyMap - methodPicker methodpicker.MethodPicker[string] - order int - endpoint database.Endpoint + width int + focused reqFocused + components map[reqFocused]componenttypes.ComponentInterface + index int + height int + help help.Model + keys *keybinds.ListKeyMap + order int + endpoint database.Endpoint } func (r *RequestView) Init() tea.Cmd { @@ -34,7 +50,8 @@ func (r *RequestView) Name() string { } func (r *RequestView) Help() []key.Binding { - return []key.Binding{} + binds := r.components[r.focused].Help() + return append(binds, keybinds.Keys.Next) } func (r *RequestView) GetFooterSegment() string { @@ -48,35 +65,48 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case tea.WindowSizeMsg: r.height = msg.Height r.width = msg.Width + r.components[urlInput].SetSize(r.width-20, r.height) case tea.KeyMsg: - log.Info("received a key") switch { case key.Matches(msg, keybinds.Keys.Back): - log.Info("changing to ep") return r, func() tea.Msg { return messages.NavigateToView{ ViewName: Endpoints, Target: Endpoints, } } + case key.Matches(msg, keybinds.Keys.Next): + r.index = (r.index + 1) % len(componentList) + r.components[r.focused].OnBlur() + r.focused = componentList[r.index] + r.components[r.focused].OnFocus() + return r, nil } } - r.methodPicker, cmd = r.methodPicker.Update(msg) + r.components[r.focused], cmd = r.components[r.focused].Update(msg) cmds = append(cmds, cmd) return r, tea.Batch(cmds...) } func (r *RequestView) View() string { - return styles.RequestLayout(10, 100)(r.methodPicker.View()) + views := []string{} + for _, val := range componentList { + views = append(views, r.components[val].View()) + } + view := lipgloss.JoinHorizontal( + lipgloss.Left, + views..., + ) + return styles.RequestLayout(r.height, r.width)(view) } func (r *RequestView) SetState(items ...any) error { if len(items) != 1 { return errors.New("Incorrect amount of fields supplied") } - r.SetState(items[0]) + // r.SetState(items[0]) return nil } @@ -85,11 +115,11 @@ func (r *RequestView) Order() int { } func (r *RequestView) OnFocus() { - r.methodPicker.OnFocus() + r.components[r.focused].OnFocus() } func (r *RequestView) OnBlur() { - r.methodPicker.OnBlur() + r.components[r.focused].OnBlur() } func methodItemMapper(items []string) []list.Item { @@ -104,27 +134,15 @@ func methodItemMapper(items []string) []list.Item { } func NewRequestView() *RequestView { - methods := []string{ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE", - } + mpConfig := createMethodPickerConfig() + + uiConfig := createURLInputConfig() - config := methodpicker.MethodPickerConfig[string]{ - Items: methods, - ItemMapper: methodItemMapper, - FilteringEnabled: false, - ShowPagination: false, - ShowStatusBar: false, - Delegate: createRequestDelegate, - ShowHelp: false, - ShowTitle: false, - Width: 30, - Height: 1, - } return &RequestView{ - methodPicker: methodpicker.NewMethodPicker[string](config), + components: map[reqFocused]componenttypes.ComponentInterface{ + methodPicker: methodpicker.NewMethodPicker(mpConfig), + urlInput: urlinput.NewUrlInput(uiConfig), + }, + focused: methodPicker, } } From 07f3c55a41a8b744fc3495847021eee170ff0e39 Mon Sep 17 00:00:00 2001 From: Yash Ranjan Date: Fri, 27 Feb 2026 15:20:24 +0100 Subject: [PATCH 08/46] Additions - URL box width fixed - Request view now updates based on ep selection - Changes to request view now save - Changes to request view cause items in the ep list to update - Added a placeholder for the url box --- .../tui/components/ComponentTypes/types.go | 10 ++- .../tui/components/MethodPicker/component.go | 33 +++++++++- .../components/OptionsProvider/component.go | 14 ++++- .../tui/components/OptionsProvider/config.go | 1 + internal/tui/components/UrlInput/component.go | 22 +++++-- internal/tui/keybinds/keys.go | 13 ++-- internal/tui/views/endpoints-view.go | 17 ++++- internal/tui/views/request-view.go | 63 ++++++++++++++----- 8 files changed, 142 insertions(+), 31 deletions(-) diff --git a/internal/tui/components/ComponentTypes/types.go b/internal/tui/components/ComponentTypes/types.go index 3e5ffcd..2c5c8c9 100644 --- a/internal/tui/components/ComponentTypes/types.go +++ b/internal/tui/components/ComponentTypes/types.go @@ -3,13 +3,17 @@ package componenttypes import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/backend/endpoints" ) -type ComponentInterface interface { +type ReqViewComponent interface { Init() tea.Cmd - Update(tea.Msg) (ComponentInterface, tea.Cmd) + Update(tea.Msg) (ReqViewComponent, tea.Cmd) View() string - SetSize(int, int) + SetWidth(int) + UpdateState(endpoints.EndpointEntity) endpoints.EndpointEntity + SetState(endpoints.EndpointEntity) + GetWidth() int Help() []key.Binding OnFocus() OnBlur() diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go index f954b4b..d73dc15 100644 --- a/internal/tui/components/MethodPicker/component.go +++ b/internal/tui/components/MethodPicker/component.go @@ -4,6 +4,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/backend/endpoints" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" "github.com/maniac-en/req/internal/tui/keybinds" ) @@ -13,6 +14,7 @@ type MethodPicker[T any] struct { onSelectAction tea.Msg delegateGenner func(bool) list.ItemDelegate keys *keybinds.ListKeyMap + state endpoints.EndpointEntity width int height int itemMapper func([]T) []list.Item @@ -32,10 +34,37 @@ func (m *MethodPicker[T]) Init() tea.Cmd { return nil } -func (m *MethodPicker[T]) SetSize(width, height int) { +func (m *MethodPicker[T]) SetWidth(width int) { + m.list.SetWidth(width) } -func (m *MethodPicker[T]) Update(msg tea.Msg) (componenttypes.ComponentInterface, tea.Cmd) { +func (m *MethodPicker[T]) GetWidth() int { + return m.list.Width() +} + +func (m *MethodPicker[T]) SetState(ep endpoints.EndpointEntity) { + if index := m.findIndex(ep.Method); index >= 0 { + m.list.Select(index) + m.state = ep + } +} + +func (m *MethodPicker[T]) UpdateState(ep endpoints.EndpointEntity) endpoints.EndpointEntity { + ep.Method = m.list.SelectedItem().(MethodOption).Name + return ep +} + +func (m *MethodPicker[T]) findIndex(method string) int { + items := m.list.Items() + for index, item := range items { + if option, ok := item.(MethodOption); ok && option.Name == method { + return index + } + } + return -1 +} + +func (m *MethodPicker[T]) Update(msg tea.Msg) (componenttypes.ReqViewComponent, tea.Cmd) { var cmd tea.Cmd m.list, cmd = m.list.Update(msg) diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index fc03a7b..d0bff2e 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -23,6 +23,7 @@ type OptionsProvider[T, U any] struct { list list.Model input OptionsInput onSelectAction tea.Msg + onChangeAction func(int64) delegateGenner func(bool) list.ItemDelegate keys *keybinds.ListKeyMap width int @@ -44,7 +45,7 @@ func (o Option) Description() string { return o.Subtext } func (o Option) Value() int64 { return o.ID } func (o Option) FilterValue() string { return o.Name } -func (o OptionsProvider[T, U]) Init() tea.Cmd { +func (o *OptionsProvider[T, U]) Init() tea.Cmd { return nil } @@ -96,7 +97,12 @@ func (o OptionsProvider[T, U]) Update(msg tea.Msg) (OptionsProvider[T, U], tea.C } switch o.focused { case listComponent: + index := o.list.Index() o.list, cmd = o.list.Update(msg) + newIndex := o.list.Index() + if newIndex != index && o.onChangeAction != nil { + o.onChangeAction(o.GetSelected().ID) + } case textComponent: o.input, cmd = o.input.Update(msg) } @@ -207,6 +213,10 @@ func (o *OptionsProvider[T, U]) SetGetItemsFunc(getItems func(context.Context) ( log.Error("error fetching items") } o.list.SetItems(o.itemMapper(items)) + if o.onChangeAction != nil { + o.onChangeAction(o.itemMapper(items)[0].(Option).ID) + } + } func NewOptionsProvider[T, U any](config *ListConfig[T, U]) OptionsProvider[T, U] { @@ -230,5 +240,7 @@ func NewOptionsProvider[T, U any](config *ListConfig[T, U]) OptionsProvider[T, U keys: config.AdditionalKeymaps, source: config.Source, delegateGenner: config.Delegate, + onChangeAction: config.OnChangeAction, } + } diff --git a/internal/tui/components/OptionsProvider/config.go b/internal/tui/components/OptionsProvider/config.go index 9ae7f4b..8cc7469 100644 --- a/internal/tui/components/OptionsProvider/config.go +++ b/internal/tui/components/OptionsProvider/config.go @@ -12,6 +12,7 @@ import ( type ListConfig[T, U any] struct { OnSelectAction tea.Msg + OnChangeAction func(int64) ShowPagination bool ShowStatusBar bool diff --git a/internal/tui/components/UrlInput/component.go b/internal/tui/components/UrlInput/component.go index 23d5ad2..f80e40d 100644 --- a/internal/tui/components/UrlInput/component.go +++ b/internal/tui/components/UrlInput/component.go @@ -5,6 +5,7 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/maniac-en/req/internal/backend/endpoints" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" ) @@ -20,16 +21,28 @@ func (u *UrlInput) Init() tea.Cmd { return nil } -func (u *UrlInput) SetSize(width, height int) { - u.text.PromptStyle.Height(height) - u.text.PromptStyle.Width(width) +func (u *UrlInput) SetWidth(width int) { + u.text.Width = width +} + +func (u *UrlInput) GetWidth() int { + return u.text.Width } func (u *UrlInput) Help() []key.Binding { return []key.Binding{} } -func (u *UrlInput) Update(msg tea.Msg) (componenttypes.ComponentInterface, tea.Cmd) { +func (u *UrlInput) SetState(ep endpoints.EndpointEntity) { + u.text.SetValue(ep.Url) +} + +func (u *UrlInput) UpdateState(ep endpoints.EndpointEntity) endpoints.EndpointEntity { + ep.Url = u.text.Value() + return ep +} + +func (u *UrlInput) Update(msg tea.Msg) (componenttypes.ReqViewComponent, tea.Cmd) { var cmd tea.Cmd u.text, cmd = u.text.Update(msg) @@ -47,6 +60,7 @@ func (u *UrlInput) OnFocus() { } func (u *UrlInput) OnBlur() { + u.text.Blur() u.active = false } diff --git a/internal/tui/keybinds/keys.go b/internal/tui/keybinds/keys.go index 99f5b08..661eb61 100644 --- a/internal/tui/keybinds/keys.go +++ b/internal/tui/keybinds/keys.go @@ -15,6 +15,7 @@ type Keymaps struct { Down key.Binding NextPage key.Binding Next key.Binding + Prev key.Binding PrevPage key.Binding Filter key.Binding ClearFilter key.Binding @@ -25,16 +26,20 @@ type Keymaps struct { var Keys = Keymaps{ Back: key.NewBinding( - key.WithKeys("esc", "q"), - key.WithHelp("esc/q", "back"), + key.WithKeys("ctrl+b"), + key.WithHelp("esc", "back"), ), Up: key.NewBinding( key.WithKeys("up", "k"), key.WithHelp("↑/k", "back"), ), + Prev: key.NewBinding( + key.WithKeys("ctrl+h"), + key.WithHelp("ctrl+h", "prev field"), + ), Next: key.NewBinding( - key.WithKeys("tab"), - key.WithHelp("tab", "next field"), + key.WithKeys("ctrl+l"), + key.WithHelp("ctrl+l", "next field"), ), Down: key.NewBinding( key.WithKeys("down", "j"), diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index ee1d460..cfd2071 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -62,7 +62,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.height = msg.Height e.width = msg.Width e.list, cmd = e.list.Update(msg) - w, _ := e.list.GetSize() + _, w := e.list.GetSize() e.requestView, cmd = e.requestView.Update(tea.WindowSizeMsg{ Height: msg.Height, Width: msg.Width - w, @@ -74,6 +74,8 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { Name: msg.Item, Method: "GET", }) + case messages.RefreshItemsList: + e.list.RefreshItems() case messages.ItemEdited: e.manager.UpdateEndpointName(context.Background(), msg.ItemID, msg.Item) case messages.DeleteItem: @@ -129,6 +131,7 @@ func (e *EndpointsView) OnFocus() { } func (e *EndpointsView) SetState(items ...any) error { + // TODO: Something over here should also set the state for the request view if len(items) == 1 { if collection, ok := items[0].(optionsProvider.Option); ok { e.collection = collection @@ -177,6 +180,17 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin keybinds := keybinds.NewListKeyMap() config := defaultListConfig[endpoints.EndpointEntity, database.Endpoint](keybinds, createEndpointsDelegate) + epUpdateFunc := epManager.UpdateEndpoint + view.requestView = NewRequestView(epUpdateFunc) + + config.OnChangeAction = func(id int64) { + epDetails, err := epManager.Read(context.Background(), id) + if err != nil { + // TODO: what do we do here? prob throw a crash ig? + } + view.requestView.SetState(epDetails) + } + epListFunc := func(ctx context.Context) ([]endpoints.EndpointEntity, error) { return epManager.ListByCollection(ctx, view.collection.ID) } @@ -187,7 +201,6 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin config.Source = "endpoints" view.list = optionsProvider.NewOptionsProvider(config) - view.requestView = NewRequestView() view.focused = listView return view diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 5416558..f6f740c 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -1,6 +1,7 @@ package views import ( + "context" "errors" "github.com/charmbracelet/bubbles/help" @@ -8,7 +9,7 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/maniac-en/req/internal/backend/database" + "github.com/maniac-en/req/internal/backend/endpoints" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" @@ -32,13 +33,14 @@ var componentList = []reqFocused{ type RequestView struct { width int focused reqFocused - components map[reqFocused]componenttypes.ComponentInterface + components map[reqFocused]componenttypes.ReqViewComponent index int height int help help.Model keys *keybinds.ListKeyMap order int - endpoint database.Endpoint + update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) + endpoint endpoints.EndpointEntity } func (r *RequestView) Init() tea.Cmd { @@ -65,7 +67,8 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case tea.WindowSizeMsg: r.height = msg.Height r.width = msg.Width - r.components[urlInput].SetSize(r.width-20, r.height) + w := r.components[urlInput].GetWidth() + r.components[urlInput].SetWidth(r.width - (w + 30)) case tea.KeyMsg: switch { case key.Matches(msg, keybinds.Keys.Back): @@ -76,11 +79,15 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { } } case key.Matches(msg, keybinds.Keys.Next): - r.index = (r.index + 1) % len(componentList) - r.components[r.focused].OnBlur() - r.focused = componentList[r.index] - r.components[r.focused].OnFocus() - return r, nil + r.shift(true) + return r, func() tea.Msg { + return messages.RefreshItemsList{} + } + case key.Matches(msg, keybinds.Keys.Prev): + r.shift(false) + return r, func() tea.Msg { + return messages.RefreshItemsList{} + } } } @@ -90,6 +97,26 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return r, tea.Batch(cmds...) } +func (r *RequestView) shift(next bool) { + if next { + r.index = (r.index + 1) % len(componentList) + } else { + r.index = (r.index - 1) % len(componentList) + } + r.endpoint = r.components[r.focused].UpdateState(r.endpoint) + r.components[r.focused].OnBlur() + r.focused = componentList[r.index] + r.components[r.focused].OnFocus() + r.update( + context.Background(), + r.endpoint.GetID(), + endpoints.EndpointData{ + Name: r.endpoint.Name, + Method: r.endpoint.Method, + URL: r.endpoint.Url, + }) +} + func (r *RequestView) View() string { views := []string{} for _, val := range componentList { @@ -103,11 +130,16 @@ func (r *RequestView) View() string { } func (r *RequestView) SetState(items ...any) error { - if len(items) != 1 { - return errors.New("Incorrect amount of fields supplied") + if len(items) == 1 { + if ep, ok := items[0].(endpoints.EndpointEntity); ok { + r.endpoint = ep + for _, val := range componentList { + r.components[val].SetState(ep) + } + return nil + } } - // r.SetState(items[0]) - return nil + return errors.New("Invalid inputs, this function takes 1 input of type endpoints.Entity") } func (r *RequestView) Order() int { @@ -133,16 +165,17 @@ func methodItemMapper(items []string) []list.Item { return listItems } -func NewRequestView() *RequestView { +func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error)) *RequestView { mpConfig := createMethodPickerConfig() uiConfig := createURLInputConfig() return &RequestView{ - components: map[reqFocused]componenttypes.ComponentInterface{ + components: map[reqFocused]componenttypes.ReqViewComponent{ methodPicker: methodpicker.NewMethodPicker(mpConfig), urlInput: urlinput.NewUrlInput(uiConfig), }, focused: methodPicker, + update: update, } } From f7b365c4114fe7c426c867091fbd8a9287b4e828 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 27 Feb 2026 23:37:06 +0100 Subject: [PATCH 09/46] Additions: - Skeleton for a response view added - You can now send requests and see the response for it --- internal/tui/keybinds/keys.go | 14 ++- internal/tui/styles/request-styles.go | 4 + internal/tui/views/endpoints-view.go | 1 + internal/tui/views/request-view.go | 134 +++++++++++++++++++++----- 4 files changed, 125 insertions(+), 28 deletions(-) diff --git a/internal/tui/keybinds/keys.go b/internal/tui/keybinds/keys.go index 661eb61..dcc1193 100644 --- a/internal/tui/keybinds/keys.go +++ b/internal/tui/keybinds/keys.go @@ -14,8 +14,10 @@ type Keymaps struct { Up key.Binding Down key.Binding NextPage key.Binding + SendRequest key.Binding Next key.Binding Prev key.Binding + Save key.Binding PrevPage key.Binding Filter key.Binding ClearFilter key.Binding @@ -27,11 +29,19 @@ type Keymaps struct { var Keys = Keymaps{ Back: key.NewBinding( key.WithKeys("ctrl+b"), - key.WithHelp("esc", "back"), + key.WithHelp("ctrl+b", "back"), + ), + SendRequest: key.NewBinding( + key.WithKeys("ctrl+r"), + key.WithHelp("ctrl+r", "send request"), + ), + Save: key.NewBinding( + key.WithKeys("ctrl+s"), + key.WithHelp("ctrl+s", "save"), ), Up: key.NewBinding( key.WithKeys("up", "k"), - key.WithHelp("↑/k", "back"), + key.WithHelp("↑/k", "up"), ), Prev: key.NewBinding( key.WithKeys("ctrl+h"), diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index 9819472..af44b01 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -26,6 +26,10 @@ func UrlInputStyle(active bool) lipgloss.Style { BorderForeground(color) } +func ResponseStyle(height, width int) func(...string) string { + return lipgloss.NewStyle().Margin(0, 0, 0, 5).Border(lipgloss.NormalBorder(), false, false, false, true).Padding(1, 0, 0, 5).Height(height).Width(width).Render +} + var ( ActiveRequestItem = lipgloss.NewStyle().Foreground(accent) InactiveRequestItem = lipgloss.NewStyle() diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index cfd2071..7e6412f 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -107,6 +107,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { } case requestView: e.requestView, cmd = e.requestView.Update(msg) + return e, nil } } } diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index f6f740c..327f34e 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -3,13 +3,17 @@ package views import ( "context" "errors" + "fmt" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/endpoints" + "github.com/maniac-en/req/internal/backend/http" + "github.com/maniac-en/req/internal/log" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" @@ -31,16 +35,21 @@ var componentList = []reqFocused{ } type RequestView struct { - width int - focused reqFocused - components map[reqFocused]componenttypes.ReqViewComponent - index int - height int - help help.Model - keys *keybinds.ListKeyMap - order int - update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) - endpoint endpoints.EndpointEntity + width int + focused reqFocused + components map[reqFocused]componenttypes.ReqViewComponent + index int + height int + loading bool + help help.Model + keys *keybinds.ListKeyMap + spinner spinner.Model + responsePage bool + client *http.HTTPManager + order int + res *http.Response + update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) + endpoint endpoints.EndpointEntity } func (r *RequestView) Init() tea.Cmd { @@ -53,7 +62,13 @@ func (r *RequestView) Name() string { func (r *RequestView) Help() []key.Binding { binds := r.components[r.focused].Help() - return append(binds, keybinds.Keys.Next) + reqViewBinds := []key.Binding{ + keybinds.Keys.Prev, + keybinds.Keys.Next, + keybinds.Keys.Save, + keybinds.Keys.SendRequest, + } + return append(binds, reqViewBinds...) } func (r *RequestView) GetFooterSegment() string { @@ -72,10 +87,15 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, keybinds.Keys.Back): - return r, func() tea.Msg { - return messages.NavigateToView{ - ViewName: Endpoints, - Target: Endpoints, + if r.responsePage { + r.responsePage = false + return r, nil + } else { + return r, func() tea.Msg { + return messages.NavigateToView{ + ViewName: Endpoints, + Target: Endpoints, + } } } case key.Matches(msg, keybinds.Keys.Next): @@ -88,12 +108,47 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return r, func() tea.Msg { return messages.RefreshItemsList{} } + case key.Matches(msg, keybinds.Keys.SendRequest): + request := &http.Request{ + Method: r.endpoint.Method, + URL: r.endpoint.Url, + } + r.responsePage = true + r.loading = true + res, err := r.client.ExecuteRequest(request) + r.loading = false + r.res = res + if err != nil { + log.Warn("error occurred while trying to send a request", err) + } + return r, r.spinner.Tick + case key.Matches(msg, keybinds.Keys.Save): + for _, val := range r.components { + r.endpoint = val.UpdateState(r.endpoint) + } + r.update( + context.Background(), + r.endpoint.GetID(), + endpoints.EndpointData{ + Name: r.endpoint.Name, + Method: r.endpoint.Method, + URL: r.endpoint.Url, + }) } } - r.components[r.focused], cmd = r.components[r.focused].Update(msg) + if !r.responsePage { + r.components[r.focused], cmd = r.components[r.focused].Update(msg) + } + cmds = append(cmds, cmd) + if r.loading { + log.Info("working") + r.spinner, cmd = r.spinner.Update(msg) + cmds = append(cmds, cmd) + } + return r, tea.Batch(cmds...) } @@ -118,15 +173,37 @@ func (r *RequestView) shift(next bool) { } func (r *RequestView) View() string { - views := []string{} - for _, val := range componentList { - views = append(views, r.components[val].View()) + if r.responsePage { + if r.res == nil { + return styles.RequestLayout(r.height, r.width)(lipgloss.Place(r.width, r.height-1, lipgloss.Center, lipgloss.Center, "No responses so far")) + } + if r.loading { + loadingString := fmt.Sprintf("%s Loading...", r.spinner.View()) + return styles.RequestLayout(r.height, r.width)(lipgloss.Place(r.width, r.height-1, lipgloss.Center, lipgloss.Center, loadingString)) + } + // TODO: use a viewport here PLEASE => https://github.com/charmbracelet/bubbles?tab=readme-ov-file#viewport + responseDetails := []string{ + fmt.Sprintf("Status Code: %d", r.res.StatusCode), + fmt.Sprintf("Body: %s", r.res.Body), + } + for key, val := range r.res.Headers { + for _, item := range val { + responseDetails = append(responseDetails, fmt.Sprintf("%s: %s", key, item)) + } + } + view := styles.ResponseStyle(r.height, r.width)(lipgloss.JoinVertical(lipgloss.Top, responseDetails...)) + return view + } else { + views := []string{} + for _, val := range componentList { + views = append(views, r.components[val].View()) + } + view := lipgloss.JoinHorizontal( + lipgloss.Left, + views..., + ) + return styles.RequestLayout(r.height, r.width)(view) } - view := lipgloss.JoinHorizontal( - lipgloss.Left, - views..., - ) - return styles.RequestLayout(r.height, r.width)(view) } func (r *RequestView) SetState(items ...any) error { @@ -169,13 +246,18 @@ func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) mpConfig := createMethodPickerConfig() uiConfig := createURLInputConfig() + s := spinner.New() + s.Spinner = spinner.Dot return &RequestView{ components: map[reqFocused]componenttypes.ReqViewComponent{ methodPicker: methodpicker.NewMethodPicker(mpConfig), urlInput: urlinput.NewUrlInput(uiConfig), }, - focused: methodPicker, - update: update, + focused: methodPicker, + update: update, + client: http.NewHTTPManager(), + responsePage: false, + spinner: s, } } From 6c1ba9f7b2daa1d99cf32b7e96906759d5eed6d4 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Sun, 1 Mar 2026 19:43:41 +0100 Subject: [PATCH 10/46] minor bug fix --- internal/tui/views/endpoints-view.go | 16 ++++++---------- internal/tui/views/request-view.go | 11 +++++------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 7e6412f..5e84a8b 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -96,21 +96,17 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, keybinds.Keys.Back): - navMsg := messages.NavigateToView{ - ViewName: Collections, - Target: MainModel, - } - switch e.focused { - case listView: + if e.focused == listView { return e, func() tea.Msg { - return navMsg + return messages.NavigateToView{ + ViewName: Collections, + Target: MainModel, + } } - case requestView: - e.requestView, cmd = e.requestView.Update(msg) - return e, nil } } } + switch e.focused { case listView: e.list, cmd = e.list.Update(msg) diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 327f34e..89edd56 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -90,12 +90,11 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { if r.responsePage { r.responsePage = false return r, nil - } else { - return r, func() tea.Msg { - return messages.NavigateToView{ - ViewName: Endpoints, - Target: Endpoints, - } + } + return r, func() tea.Msg { + return messages.NavigateToView{ + ViewName: Endpoints, + Target: Endpoints, } } case key.Matches(msg, keybinds.Keys.Next): From 2f40563533bad0fde6893fa0603cec390c07325a Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Sun, 1 Mar 2026 23:32:22 +0100 Subject: [PATCH 11/46] viewport component added, needs more polishing --- internal/tui/components/ViewPort/component.go | 82 +++++++++++++++++++ internal/tui/views/request-view.go | 28 +++---- 2 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 internal/tui/components/ViewPort/component.go diff --git a/internal/tui/components/ViewPort/component.go b/internal/tui/components/ViewPort/component.go new file mode 100644 index 0000000..0533bf1 --- /dev/null +++ b/internal/tui/components/ViewPort/component.go @@ -0,0 +1,82 @@ +package viewport + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/backend/http" +) + +type Viewport struct { + viewport viewport.Model + width int + height int + ready bool + state string +} + +func (v *Viewport) Init() tea.Cmd { + return nil +} + +func (v *Viewport) SetState(state *http.Response) { + var str strings.Builder + + str.WriteString(fmt.Sprintf("Status code: %d\n", state.StatusCode)) + str.WriteString(fmt.Sprintf("ContentType header: %s\n", state.Headers["Content-Type"])) + str.WriteString(fmt.Sprintf("Body: %s\n", state.Body)) + + v.state = str.String() + v.viewport.SetContent(v.state) +} + +func (v Viewport) Update(msg tea.Msg) (Viewport, tea.Cmd) { + var cmd tea.Cmd + var cmds []tea.Cmd + + switch msg := msg.(type) { + + case tea.WindowSizeMsg: + if !v.ready { + v.viewport = viewport.New(msg.Width, msg.Height-10) + v.ready = true + } else { + v.viewport.Width = msg.Width + v.viewport.Height = msg.Height - 10 + } + } + + v.viewport, cmd = v.viewport.Update(msg) + + cmds = append(cmds, cmd) + + return v, tea.Batch(cmds...) +} + +func (v *Viewport) View() string { + if !v.ready { + return "Waiting.." + } + return v.viewport.View() +} + +func (v *Viewport) OnFocus() { +} + +func (v *Viewport) OnBlur() { +} + +func (v *Viewport) Help() []key.Binding { + return []key.Binding{} +} + +func NewViewport() Viewport { + return Viewport{ + viewport: viewport.New(1, 1), + ready: false, + state: "No response received yet :(", + } +} diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 89edd56..f420155 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -17,6 +17,7 @@ import ( componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" + viewport "github.com/maniac-en/req/internal/tui/components/ViewPort" "github.com/maniac-en/req/internal/tui/keybinds" "github.com/maniac-en/req/internal/tui/messages" "github.com/maniac-en/req/internal/tui/styles" @@ -37,6 +38,7 @@ var componentList = []reqFocused{ type RequestView struct { width int focused reqFocused + viewport viewport.Viewport components map[reqFocused]componenttypes.ReqViewComponent index int height int @@ -47,7 +49,6 @@ type RequestView struct { responsePage bool client *http.HTTPManager order int - res *http.Response update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) endpoint endpoints.EndpointEntity } @@ -84,6 +85,10 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { r.width = msg.Width w := r.components[urlInput].GetWidth() r.components[urlInput].SetWidth(r.width - (w + 30)) + r.viewport, cmd = r.viewport.Update(tea.WindowSizeMsg{ + Height: msg.Height, + Width: r.width, + }) case tea.KeyMsg: switch { case key.Matches(msg, keybinds.Keys.Back): @@ -116,10 +121,10 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { r.loading = true res, err := r.client.ExecuteRequest(request) r.loading = false - r.res = res if err != nil { log.Warn("error occurred while trying to send a request", err) } + r.viewport.SetState(res) return r, r.spinner.Tick case key.Matches(msg, keybinds.Keys.Save): for _, val := range r.components { @@ -138,12 +143,13 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { if !r.responsePage { r.components[r.focused], cmd = r.components[r.focused].Update(msg) + } else { + r.viewport, cmd = r.viewport.Update(msg) } cmds = append(cmds, cmd) if r.loading { - log.Info("working") r.spinner, cmd = r.spinner.Update(msg) cmds = append(cmds, cmd) } @@ -173,24 +179,11 @@ func (r *RequestView) shift(next bool) { func (r *RequestView) View() string { if r.responsePage { - if r.res == nil { - return styles.RequestLayout(r.height, r.width)(lipgloss.Place(r.width, r.height-1, lipgloss.Center, lipgloss.Center, "No responses so far")) - } if r.loading { loadingString := fmt.Sprintf("%s Loading...", r.spinner.View()) return styles.RequestLayout(r.height, r.width)(lipgloss.Place(r.width, r.height-1, lipgloss.Center, lipgloss.Center, loadingString)) } - // TODO: use a viewport here PLEASE => https://github.com/charmbracelet/bubbles?tab=readme-ov-file#viewport - responseDetails := []string{ - fmt.Sprintf("Status Code: %d", r.res.StatusCode), - fmt.Sprintf("Body: %s", r.res.Body), - } - for key, val := range r.res.Headers { - for _, item := range val { - responseDetails = append(responseDetails, fmt.Sprintf("%s: %s", key, item)) - } - } - view := styles.ResponseStyle(r.height, r.width)(lipgloss.JoinVertical(lipgloss.Top, responseDetails...)) + view := styles.ResponseStyle(r.height, r.width)(r.viewport.View()) return view } else { views := []string{} @@ -254,6 +247,7 @@ func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) urlInput: urlinput.NewUrlInput(uiConfig), }, focused: methodPicker, + viewport: viewport.NewViewport(), update: update, client: http.NewHTTPManager(), responsePage: false, From 3fb8073f808d4ffc50bdc839cf3a09d22e23d743 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Mon, 2 Mar 2026 01:03:21 +0100 Subject: [PATCH 12/46] bug fixes --- internal/tui/app/model.go | 12 +++++++++++ .../components/OptionsProvider/component.go | 20 ++++++++++++++----- .../tui/components/OptionsProvider/config.go | 1 + internal/tui/components/ViewPort/component.go | 2 +- internal/tui/views/collections-view.go | 1 + internal/tui/views/endpoints-view.go | 7 ++++++- internal/tui/views/request-view.go | 3 +++ 7 files changed, 39 insertions(+), 7 deletions(-) diff --git a/internal/tui/app/model.go b/internal/tui/app/model.go index 07e8f8f..fcc4e88 100644 --- a/internal/tui/app/model.go +++ b/internal/tui/app/model.go @@ -47,6 +47,10 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd var cmds []tea.Cmd switch msg := msg.(type) { + case messages.DeleteItem: + a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) + case messages.ItemAdded: + a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) case tea.WindowSizeMsg: a.height = msg.Height a.width = msg.Width @@ -97,6 +101,14 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.Views[a.focusedView], cmd = a.Views[a.focusedView].Update(msg) cmds = append(cmds, cmd) + // additional update for collections list to update when an ep is added + switch msg.(type) { + case messages.DeleteItem: + a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) + case messages.ItemAdded: + a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) + } + return a, tea.Batch(cmds...) } diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index d0bff2e..f3f13d8 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -133,7 +133,17 @@ func (o OptionsProvider[T, U]) GetSelected() Option { Subtext: "", } } - return o.list.SelectedItem().(Option) + + item := o.list.SelectedItem() + + if item == nil { + return Option{ + Name: "No selection", + ID: -1, + Subtext: "", + } + } + return item.(Option) } func (o OptionsProvider[T, U]) IsFiltering() bool { @@ -193,7 +203,7 @@ func initList[T, U any](config *ListConfig[T, U]) list.Model { items := config.ItemMapper(rawItems) - list := list.New(items, config.Delegate(true), 30, 30) + list := list.New(items, config.Delegate(true), 100, 30) // list configuration list.SetFilteringEnabled(config.FilteringEnabled) @@ -213,8 +223,8 @@ func (o *OptionsProvider[T, U]) SetGetItemsFunc(getItems func(context.Context) ( log.Error("error fetching items") } o.list.SetItems(o.itemMapper(items)) - if o.onChangeAction != nil { - o.onChangeAction(o.itemMapper(items)[0].(Option).ID) + if mappedItems := o.itemMapper(items); o.onChangeAction != nil && len(mappedItems) > 0 { + o.onChangeAction(mappedItems[0].(Option).ID) } } @@ -222,7 +232,7 @@ func (o *OptionsProvider[T, U]) SetGetItemsFunc(getItems func(context.Context) ( func NewOptionsProvider[T, U any](config *ListConfig[T, U]) OptionsProvider[T, U] { inputConfig := InputConfig{ CharLimit: 100, - Placeholder: "Add A New Collection...", + Placeholder: config.Placeholder, Width: 22, Prompt: "", KeyMap: InputKeyMaps{ diff --git a/internal/tui/components/OptionsProvider/config.go b/internal/tui/components/OptionsProvider/config.go index 8cc7469..cdacf5e 100644 --- a/internal/tui/components/OptionsProvider/config.go +++ b/internal/tui/components/OptionsProvider/config.go @@ -22,6 +22,7 @@ type ListConfig[T, U any] struct { FilteringEnabled bool + Placeholder string Delegate func(bool) list.ItemDelegate KeyMap list.KeyMap AdditionalKeymaps *keybinds.ListKeyMap diff --git a/internal/tui/components/ViewPort/component.go b/internal/tui/components/ViewPort/component.go index 0533bf1..3aa1b3f 100644 --- a/internal/tui/components/ViewPort/component.go +++ b/internal/tui/components/ViewPort/component.go @@ -41,7 +41,7 @@ func (v Viewport) Update(msg tea.Msg) (Viewport, tea.Cmd) { case tea.WindowSizeMsg: if !v.ready { - v.viewport = viewport.New(msg.Width, msg.Height-10) + v.viewport = viewport.New(msg.Width, msg.Height-20) v.ready = true } else { v.viewport.Width = msg.Width diff --git a/internal/tui/views/collections-view.go b/internal/tui/views/collections-view.go index 0c56a39..b07e3f8 100644 --- a/internal/tui/views/collections-view.go +++ b/internal/tui/views/collections-view.go @@ -140,6 +140,7 @@ func NewCollectionsView(collManager *collections.CollectionsManager, endpointsMa } config.AdditionalKeymaps = keybinds config.Source = "collections" + config.Placeholder = "Add a new collection..." return &CollectionsView{ list: optionsProvider.NewOptionsProvider(config), diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 5e84a8b..aa8dbad 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -69,11 +69,15 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { }) cmds = append(cmds, cmd) case messages.ItemAdded: - e.manager.CreateEndpoint(context.Background(), endpoints.EndpointData{ + ep, err := e.manager.CreateEndpoint(context.Background(), endpoints.EndpointData{ CollectionID: e.collection.ID, Name: msg.Item, Method: "GET", }) + if err != nil { + //TODO: handle this + } + e.requestView.SetState(ep) case messages.RefreshItemsList: e.list.RefreshItems() case messages.ItemEdited: @@ -196,6 +200,7 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin config.ItemMapper = itemMapperEp config.AdditionalKeymaps = keybinds config.Source = "endpoints" + config.Placeholder = "Add a new endpoint..." view.list = optionsProvider.NewOptionsProvider(config) view.focused = listView diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index f420155..74d556d 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -186,6 +186,9 @@ func (r *RequestView) View() string { view := styles.ResponseStyle(r.height, r.width)(r.viewport.View()) return view } else { + if r.endpoint.Name == "" { + return styles.RequestLayout(r.height, r.width)("No Endpoint selected") + } views := []string{} for _, val := range componentList { views = append(views, r.components[val].View()) From be0638b7bf95fe6bd3d3bb456ef8f8eefc858721 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Mon, 2 Mar 2026 01:17:56 +0100 Subject: [PATCH 13/46] bug fixes --- internal/tui/components/ViewPort/component.go | 6 +++++- internal/tui/views/endpoints-view.go | 1 + internal/tui/views/request-view.go | 8 +++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/tui/components/ViewPort/component.go b/internal/tui/components/ViewPort/component.go index 3aa1b3f..e4e45ed 100644 --- a/internal/tui/components/ViewPort/component.go +++ b/internal/tui/components/ViewPort/component.go @@ -7,6 +7,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/http" ) @@ -60,7 +61,9 @@ func (v *Viewport) View() string { if !v.ready { return "Waiting.." } - return v.viewport.View() + // FIX: this mess + percentage := int(v.viewport.ScrollPercent() * 100) + return lipgloss.JoinVertical(lipgloss.Top, v.viewport.View(), fmt.Sprintf("%d%%", percentage)) } func (v *Viewport) OnFocus() { @@ -70,6 +73,7 @@ func (v *Viewport) OnBlur() { } func (v *Viewport) Help() []key.Binding { + // FIX: this return []key.Binding{} } diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index aa8dbad..9b46a33 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -101,6 +101,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { switch { case key.Matches(msg, keybinds.Keys.Back): if e.focused == listView { + e.requestView.SetState(endpoints.EndpointEntity{}) return e, func() tea.Msg { return messages.NavigateToView{ ViewName: Collections, diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 74d556d..eba27bc 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -69,6 +69,7 @@ func (r *RequestView) Help() []key.Binding { keybinds.Keys.Save, keybinds.Keys.SendRequest, } + // FIX: should append responsePage binds when necessary return append(binds, reqViewBinds...) } @@ -249,9 +250,10 @@ func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) methodPicker: methodpicker.NewMethodPicker(mpConfig), urlInput: urlinput.NewUrlInput(uiConfig), }, - focused: methodPicker, - viewport: viewport.NewViewport(), - update: update, + focused: methodPicker, + viewport: viewport.NewViewport(), + update: update, + // FIX: use app context http manager client: http.NewHTTPManager(), responsePage: false, spinner: s, From 75bf1ecadaa67947c83c0c4c80241ea5ceffcdb1 Mon Sep 17 00:00:00 2001 From: Yash Ranjan Date: Fri, 28 Aug 2026 18:18:47 +0200 Subject: [PATCH 14/46] transfer --- internal/tui/components/ViewPort/component.go | 28 +++++++++++++++---- internal/tui/views/request-view.go | 22 +++++++++------ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/internal/tui/components/ViewPort/component.go b/internal/tui/components/ViewPort/component.go index e4e45ed..b30438c 100644 --- a/internal/tui/components/ViewPort/component.go +++ b/internal/tui/components/ViewPort/component.go @@ -9,6 +9,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/http" + "github.com/maniac-en/req/internal/tui/keybinds" ) type Viewport struct { @@ -26,9 +27,9 @@ func (v *Viewport) Init() tea.Cmd { func (v *Viewport) SetState(state *http.Response) { var str strings.Builder - str.WriteString(fmt.Sprintf("Status code: %d\n", state.StatusCode)) - str.WriteString(fmt.Sprintf("ContentType header: %s\n", state.Headers["Content-Type"])) - str.WriteString(fmt.Sprintf("Body: %s\n", state.Body)) + fmt.Fprintf(&str, "Status code: %d\n", state.StatusCode) + fmt.Fprintf(&str, "ContentType header: %s\n", state.Headers["Content-Type"]) + fmt.Fprintf(&str, "Body: %s\n", state.Body) v.state = str.String() v.viewport.SetContent(v.state) @@ -43,6 +44,8 @@ func (v Viewport) Update(msg tea.Msg) (Viewport, tea.Cmd) { case tea.WindowSizeMsg: if !v.ready { v.viewport = viewport.New(msg.Width, msg.Height-20) + v.width = msg.Width + v.height = msg.Height v.ready = true } else { v.viewport.Width = msg.Width @@ -62,8 +65,18 @@ func (v *Viewport) View() string { return "Waiting.." } // FIX: this mess - percentage := int(v.viewport.ScrollPercent() * 100) - return lipgloss.JoinVertical(lipgloss.Top, v.viewport.View(), fmt.Sprintf("%d%%", percentage)) + str := fmt.Sprintf(" %d%% ", int(v.viewport.ScrollPercent()*100)) + + padding := v.height - v.viewport.Height - 7 + + return lipgloss.JoinVertical(lipgloss.Left, + v.viewport.View(), + lipgloss.NewStyle(). + Width(v.width). + PaddingTop(padding). + Align(lipgloss.Right). + Render(str), + ) } func (v *Viewport) OnFocus() { @@ -74,7 +87,10 @@ func (v *Viewport) OnBlur() { func (v *Viewport) Help() []key.Binding { // FIX: this - return []key.Binding{} + return []key.Binding{ + keybinds.Keys.Down, + keybinds.Keys.Up, + } } func NewViewport() Viewport { diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index eba27bc..13fd54c 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -62,15 +62,21 @@ func (r *RequestView) Name() string { } func (r *RequestView) Help() []key.Binding { - binds := r.components[r.focused].Help() - reqViewBinds := []key.Binding{ - keybinds.Keys.Prev, - keybinds.Keys.Next, - keybinds.Keys.Save, - keybinds.Keys.SendRequest, + var reqViewBinds []key.Binding + if r.responsePage { + reqViewBinds = r.viewport.Help() + } else { + binds := r.components[r.focused].Help() + reqViewBinds = []key.Binding{ + keybinds.Keys.Prev, + keybinds.Keys.Next, + keybinds.Keys.Save, + keybinds.Keys.SendRequest, + } + reqViewBinds = append(binds, reqViewBinds...) } // FIX: should append responsePage binds when necessary - return append(binds, reqViewBinds...) + return reqViewBinds } func (r *RequestView) GetFooterSegment() string { @@ -88,7 +94,7 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { r.components[urlInput].SetWidth(r.width - (w + 30)) r.viewport, cmd = r.viewport.Update(tea.WindowSizeMsg{ Height: msg.Height, - Width: r.width, + Width: r.width - 4, }) case tea.KeyMsg: switch { From 4a9e95c4e0b0ab5652345732e34259a78d63bbc0 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 28 Aug 2026 19:32:57 +0200 Subject: [PATCH 15/46] feat: Integrate dedicated request view workflow - Adds and wires up the dedicated request view to the application model. - Refactors EndpointsView to streamline navigation to the request view. - Simplifies item delegate logic in endpoints-view-helper.go by adopting default styles. - Updates RequestView state initialization to load endpoints by ID. --- internal/tui/app/model.go | 6 ++ internal/tui/styles/collections-styles.go | 1 + internal/tui/styles/request-styles.go | 3 +- internal/tui/views/endpoints-view-helper.go | 44 ++------------- internal/tui/views/endpoints-view.go | 61 +++++++-------------- internal/tui/views/request-view.go | 17 ++++-- 6 files changed, 46 insertions(+), 86 deletions(-) diff --git a/internal/tui/app/model.go b/internal/tui/app/model.go index fcc4e88..c090660 100644 --- a/internal/tui/app/model.go +++ b/internal/tui/app/model.go @@ -21,6 +21,7 @@ type ViewName string const ( Collections ViewName = "collections" Endpoints ViewName = "endpoints" + Request ViewName = "request" ) type Heading struct { @@ -86,6 +87,7 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.focusedView = ViewName(msg.ViewName) a.Views[a.focusedView].OnFocus() return a, nil + case messages.ShowError: log.Error("user operation failed", "error", msg.Message) a.errorMsg = msg.Message @@ -196,9 +198,13 @@ func NewAppModel(ctx *Context) AppModel { help: help.New(), keys: appKeybinds, } + + epUpdateFunc := model.ctx.Endpoints.UpdateEndpoint + model.Views = map[ViewName]views.ViewInterface{ Collections: views.NewCollectionsView(model.ctx.Collections, model.ctx.Endpoints, 1), Endpoints: views.NewEndpointsView(model.ctx.Endpoints, 2), + Request: views.NewRequestView(model.ctx.Endpoints, epUpdateFunc, 3), } return model } diff --git a/internal/tui/styles/collections-styles.go b/internal/tui/styles/collections-styles.go index b4d4393..a8d8614 100644 --- a/internal/tui/styles/collections-styles.go +++ b/internal/tui/styles/collections-styles.go @@ -7,4 +7,5 @@ var ( SelectedActiveListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) + // Grad = lipg ) diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index af44b01..d712c2d 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -8,7 +8,6 @@ func RequestLayout(height, width int) func(...string) string { Width(width). Margin(0, 0, 0, 5). Padding(1, 0, 0, 10). - Border(lipgloss.NormalBorder(), false, false, false, true). Render } @@ -27,7 +26,7 @@ func UrlInputStyle(active bool) lipgloss.Style { } func ResponseStyle(height, width int) func(...string) string { - return lipgloss.NewStyle().Margin(0, 0, 0, 5).Border(lipgloss.NormalBorder(), false, false, false, true).Padding(1, 0, 0, 5).Height(height).Width(width).Render + return lipgloss.NewStyle().Margin(0, 0, 0, 5).Padding(1, 0, 0, 5).Height(height).Width(width).Render } var ( diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go index 7a4e659..d44d704 100644 --- a/internal/tui/views/endpoints-view-helper.go +++ b/internal/tui/views/endpoints-view-helper.go @@ -1,53 +1,19 @@ package views import ( - "fmt" - "io" - "strings" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/maniac-en/req/internal/tui/components/OptionsProvider" "github.com/maniac-en/req/internal/tui/styles" ) func createEndpointsDelegate(active bool) list.ItemDelegate { - d := epItemDelegate{} - + d := list.NewDefaultDelegate() if active { - d.SelectedTitleStyle = styles.SelectedActiveListStyle + d.Styles.SelectedTitle = styles.SelectedActiveListStyle } else { - d.SelectedTitleStyle = styles.SelectedInactiveListStyle - } - d.UnselectedTitleStyle = styles.UnselectedListStyle + d.Styles.SelectedTitle = styles.SelectedInactiveListStyle - return d -} - -type epItemDelegate struct { - SelectedTitleStyle lipgloss.Style - UnselectedTitleStyle lipgloss.Style -} - -func (e epItemDelegate) Height() int { return 1 } -func (e epItemDelegate) Spacing() int { return 1 } -func (e epItemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } -func (e epItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { - item, ok := listItem.(optionsProvider.Option) - - if !ok { - return - } - - str := fmt.Sprintf("%s %s", item.Subtext, item.Name) - - fn := e.UnselectedTitleStyle.Render - if index == m.Index() { - fn = func(s ...string) string { - return e.SelectedTitleStyle.Render(strings.Join(s, " ")) - } } + d.Styles.SelectedDesc = styles.UnselectedListStyle - fmt.Fprint(w, fn(str)) + return d } diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 9b46a33..e0ccc77 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -19,19 +19,17 @@ import ( type epFocused string const ( - listView = "listview" - requestView = "requestview" + listView = "listview" ) type EndpointsView struct { - height int - focused epFocused - collection optionsProvider.Option - width int - order int - list optionsProvider.OptionsProvider[endpoints.EndpointEntity, database.Endpoint] - requestView ViewInterface - manager *endpoints.EndpointsManager + height int + focused epFocused + collection optionsProvider.Option + width int + order int + list optionsProvider.OptionsProvider[endpoints.EndpointEntity, database.Endpoint] + manager *endpoints.EndpointsManager } func (e *EndpointsView) Init() tea.Cmd { @@ -43,11 +41,7 @@ func (e *EndpointsView) Name() string { } func (e *EndpointsView) Help() []key.Binding { - if e.focused == listView { - return e.list.Help() - } else { - return e.requestView.Help() - } + return e.list.Help() } func (e *EndpointsView) GetFooterSegment() string { @@ -62,14 +56,9 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.height = msg.Height e.width = msg.Width e.list, cmd = e.list.Update(msg) - _, w := e.list.GetSize() - e.requestView, cmd = e.requestView.Update(tea.WindowSizeMsg{ - Height: msg.Height, - Width: msg.Width - w, - }) cmds = append(cmds, cmd) case messages.ItemAdded: - ep, err := e.manager.CreateEndpoint(context.Background(), endpoints.EndpointData{ + _, err := e.manager.CreateEndpoint(context.Background(), endpoints.EndpointData{ CollectionID: e.collection.ID, Name: msg.Item, Method: "GET", @@ -77,7 +66,6 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { if err != nil { //TODO: handle this } - e.requestView.SetState(ep) case messages.RefreshItemsList: e.list.RefreshItems() case messages.ItemEdited: @@ -87,13 +75,19 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.list.RefreshItems() case messages.ChooseItem[optionsProvider.Option]: e.list.OnBlur() - e.requestView.OnFocus() - e.focused = requestView + return e, func() tea.Msg { + return messages.NavigateToView{ + ViewName: "request", + Target: MainModel, + Data: msg.Item.ID, + } + } case messages.NavigateToView: if msg.Target != Endpoints { break } - e.requestView.OnBlur() + // FIX: idek + // e.requestView.OnBlur() e.focused = listView e.list.OnFocus() @@ -101,7 +95,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { switch { case key.Matches(msg, keybinds.Keys.Back): if e.focused == listView { - e.requestView.SetState(endpoints.EndpointEntity{}) + // e.requestView.SetState(endpoints.EndpointEntity{}) return e, func() tea.Msg { return messages.NavigateToView{ ViewName: Collections, @@ -115,8 +109,6 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { switch e.focused { case listView: e.list, cmd = e.list.Update(msg) - case requestView: - e.requestView, cmd = e.requestView.Update(msg) } cmds = append(cmds, cmd) @@ -125,7 +117,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { } func (e *EndpointsView) View() string { - return lipgloss.JoinHorizontal(lipgloss.Top, e.list.View(), e.requestView.View()) + return e.list.View() } func (e *EndpointsView) OnFocus() { @@ -182,17 +174,6 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin keybinds := keybinds.NewListKeyMap() config := defaultListConfig[endpoints.EndpointEntity, database.Endpoint](keybinds, createEndpointsDelegate) - epUpdateFunc := epManager.UpdateEndpoint - view.requestView = NewRequestView(epUpdateFunc) - - config.OnChangeAction = func(id int64) { - epDetails, err := epManager.Read(context.Background(), id) - if err != nil { - // TODO: what do we do here? prob throw a crash ig? - } - view.requestView.SetState(epDetails) - } - epListFunc := func(ctx context.Context) ([]endpoints.EndpointEntity, error) { return epManager.ListByCollection(ctx, view.collection.ID) } diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 13fd54c..98cd792 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -41,6 +41,7 @@ type RequestView struct { viewport viewport.Viewport components map[reqFocused]componenttypes.ReqViewComponent index int + epManager *endpoints.EndpointsManager height int loading bool help help.Model @@ -210,7 +211,11 @@ func (r *RequestView) View() string { func (r *RequestView) SetState(items ...any) error { if len(items) == 1 { - if ep, ok := items[0].(endpoints.EndpointEntity); ok { + if id, ok := items[0].(int64); ok { + ep, err := r.epManager.Read(context.Background(), id) + if err != nil { + // FIX: do something over here + } r.endpoint = ep for _, val := range componentList { r.components[val].SetState(ep) @@ -244,7 +249,7 @@ func methodItemMapper(items []string) []list.Item { return listItems } -func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error)) *RequestView { +func NewRequestView(epManager *endpoints.EndpointsManager, update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error), order int) *RequestView { mpConfig := createMethodPickerConfig() uiConfig := createURLInputConfig() @@ -256,9 +261,11 @@ func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) methodPicker: methodpicker.NewMethodPicker(mpConfig), urlInput: urlinput.NewUrlInput(uiConfig), }, - focused: methodPicker, - viewport: viewport.NewViewport(), - update: update, + epManager: epManager, + focused: methodPicker, + viewport: viewport.NewViewport(), + order: order, + update: update, // FIX: use app context http manager client: http.NewHTTPManager(), responsePage: false, From 2463b9c3cbbe487a61110804a242368b0ab30b87 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 28 Aug 2026 21:47:02 +0200 Subject: [PATCH 16/46] refactor: Improve TUI architecture and state handling - Refactors AppModel update logic to correctly handle window resizing across all views. - Updates RequestView and AppModel initialization to use a structured RequestViewConfig. - Improves error handling in main and run functions for safer process termination. - Adds support for displaying HTTP response data and errors in the RequestView. --- internal/tui/app/model.go | 20 ++++++------ internal/tui/messages/messages.go | 7 +++++ internal/tui/views/endpoints-view.go | 1 - internal/tui/views/request-view.go | 47 +++++++++++++++++++--------- main.go | 14 +++++++-- 5 files changed, 60 insertions(+), 29 deletions(-) diff --git a/internal/tui/app/model.go b/internal/tui/app/model.go index c090660..77266d9 100644 --- a/internal/tui/app/model.go +++ b/internal/tui/app/model.go @@ -55,11 +55,10 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: a.height = msg.Height a.width = msg.Width - for key, _ := range a.Views { + for key := range a.Views { a.Views[key], cmd = a.Views[key].Update(tea.WindowSizeMsg{Height: a.AvailableHeight(), Width: msg.Width}) cmds = append(cmds, cmd) } - cmds = append(cmds, cmd) return a, tea.Batch(cmds...) case messages.ChooseItem[optionsProvider.Option]: switch msg.Source { @@ -103,14 +102,6 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.Views[a.focusedView], cmd = a.Views[a.focusedView].Update(msg) cmds = append(cmds, cmd) - // additional update for collections list to update when an ep is added - switch msg.(type) { - case messages.DeleteItem: - a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) - case messages.ItemAdded: - a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) - } - return a, tea.Batch(cmds...) } @@ -201,10 +192,17 @@ func NewAppModel(ctx *Context) AppModel { epUpdateFunc := model.ctx.Endpoints.UpdateEndpoint + reqCfg := views.RequestViewConfig{ + EpManager: model.ctx.Endpoints, + Update: epUpdateFunc, + Order: 3, + Client: model.ctx.HTTP, + } + model.Views = map[ViewName]views.ViewInterface{ Collections: views.NewCollectionsView(model.ctx.Collections, model.ctx.Endpoints, 1), Endpoints: views.NewEndpointsView(model.ctx.Endpoints, 2), - Request: views.NewRequestView(model.ctx.Endpoints, epUpdateFunc, 3), + Request: views.NewRequestView(reqCfg), } return model } diff --git a/internal/tui/messages/messages.go b/internal/tui/messages/messages.go index 21ad3b9..1077f33 100644 --- a/internal/tui/messages/messages.go +++ b/internal/tui/messages/messages.go @@ -1,5 +1,7 @@ package messages +import "github.com/maniac-en/req/internal/backend/http" + type ItemAdded struct { Item string } @@ -25,6 +27,11 @@ type NavigateToView struct { Data interface{} } +type Response struct { + Data *http.Response + Err error +} + type RefreshItemsList struct{} type ShowError struct { diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index e0ccc77..43fe5b9 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -8,7 +8,6 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/database" "github.com/maniac-en/req/internal/backend/endpoints" optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 98cd792..c7dcf6d 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -13,7 +13,6 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/endpoints" "github.com/maniac-en/req/internal/backend/http" - "github.com/maniac-en/req/internal/log" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" @@ -97,6 +96,12 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { Height: msg.Height, Width: r.width - 4, }) + case messages.Response: + if msg.Err != nil { + // TODO: do something here idek + } + r.SetState(msg.Data) + r.loading = false case tea.KeyMsg: switch { case key.Matches(msg, keybinds.Keys.Back): @@ -127,13 +132,18 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { } r.responsePage = true r.loading = true - res, err := r.client.ExecuteRequest(request) - r.loading = false - if err != nil { - log.Warn("error occurred while trying to send a request", err) + sendMsg := func() tea.Msg { + res, err := r.client.ExecuteRequest(request) + if err != nil { + return messages.Response{ + Err: err, + } + } + return messages.Response{ + Data: res, + } } - r.viewport.SetState(res) - return r, r.spinner.Tick + return r, tea.Batch(r.spinner.Tick, sendMsg) case key.Matches(msg, keybinds.Keys.Save): for _, val := range r.components { r.endpoint = val.UpdateState(r.endpoint) @@ -249,7 +259,14 @@ func methodItemMapper(items []string) []list.Item { return listItems } -func NewRequestView(epManager *endpoints.EndpointsManager, update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error), order int) *RequestView { +type RequestViewConfig struct { + EpManager *endpoints.EndpointsManager + Update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) + Order int + Client *http.HTTPManager +} + +func NewRequestView(cfg RequestViewConfig) *RequestView { mpConfig := createMethodPickerConfig() uiConfig := createURLInputConfig() @@ -261,13 +278,13 @@ func NewRequestView(epManager *endpoints.EndpointsManager, update func(context.C methodPicker: methodpicker.NewMethodPicker(mpConfig), urlInput: urlinput.NewUrlInput(uiConfig), }, - epManager: epManager, - focused: methodPicker, - viewport: viewport.NewViewport(), - order: order, - update: update, - // FIX: use app context http manager - client: http.NewHTTPManager(), + + epManager: cfg.EpManager, + focused: methodPicker, + viewport: viewport.NewViewport(), + order: cfg.Order, + update: cfg.Update, + client: cfg.Client, responsePage: false, spinner: s, } diff --git a/main.go b/main.go index 43de018..879eb69 100644 --- a/main.go +++ b/main.go @@ -114,6 +114,13 @@ func runMigrations() error { } func main() { + if err := run(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func run() error { // initialize paths first if err := initPaths(); err != nil { fmt.Fprintf(os.Stderr, "Failed to initialize: %v\n", err) @@ -139,11 +146,13 @@ func main() { // run database migrations if err := runMigrations(); err != nil { - log.Fatal("failed to run migrations", "error", err) + return fmt.Errorf("failed to run migrations", "error", err) } // create database client and managers db := database.New(DB) + defer DB.Close() + collectionsManager := collections.NewCollectionsManager(db) endpointsManager := endpoints.NewEndpointsManager(db) httpManager := http.NewHTTPManager() @@ -174,6 +183,7 @@ func main() { // Entry point for UI program := tea.NewProgram(app.NewAppModel(appContext), tea.WithAltScreen()) if _, err := program.Run(); err != nil { - log.Fatal("Fatal error:", err) + return fmt.Errorf("fatal error: %w", err) } + return nil } From 9483ff2200c1f39218abe755733ff3c5e752dae3 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 28 Aug 2026 21:53:04 +0200 Subject: [PATCH 17/46] chore: remove dummy data population setup - The automatic generation of demo API endpoints and collections has been removed. - This cleans up the main.go startup routine and removes the demo package dependency. - This makes the application startup flow more minimal and reduces reliance on hardcoded demo data. --- internal/backend/demo/dummy_data.go | 221 ---------------------------- main.go | 10 -- 2 files changed, 231 deletions(-) delete mode 100644 internal/backend/demo/dummy_data.go diff --git a/internal/backend/demo/dummy_data.go b/internal/backend/demo/dummy_data.go deleted file mode 100644 index 60bcfeb..0000000 --- a/internal/backend/demo/dummy_data.go +++ /dev/null @@ -1,221 +0,0 @@ -package demo - -import ( - "context" - - "github.com/maniac-en/req/internal/backend/collections" - "github.com/maniac-en/req/internal/backend/endpoints" - "github.com/maniac-en/req/internal/log" -) - -type DemoGenerator struct { - collectionsManager *collections.CollectionsManager - endpointsManager *endpoints.EndpointsManager -} - -func NewDemoGenerator(collectionsManager *collections.CollectionsManager, endpointsManager *endpoints.EndpointsManager) *DemoGenerator { - return &DemoGenerator{ - collectionsManager: collectionsManager, - endpointsManager: endpointsManager, - } -} - -func (d *DemoGenerator) PopulateDummyData(ctx context.Context) (bool, error) { - log.Info("populating dummy data for demo") - - // Check if we already have collections - result, err := d.collectionsManager.ListPaginated(ctx, 1, 0) - if err != nil { - log.Error("failed to check existing collections", "error", err) - return false, err - } - - if len(result.Collections) > 0 { - log.Debug("dummy data already exists, skipping population", "collections_count", len(result.Collections)) - return false, nil - } - - // Create demo collections and endpoints - if err := d.createJSONPlaceholderCollection(ctx); err != nil { - return false, err - } - - if err := d.createReqresCollection(ctx); err != nil { - return false, err - } - - if err := d.createHTTPBinCollection(ctx); err != nil { - return false, err - } - - log.Info("dummy data populated successfully") - return true, nil -} - -func (d *DemoGenerator) createJSONPlaceholderCollection(ctx context.Context) error { - collection, err := d.collectionsManager.Create(ctx, "JSONPlaceholder API") - if err != nil { - log.Error("failed to create JSONPlaceholder collection", "error", err) - return err - } - - endpoints := []endpoints.EndpointData{ - { - CollectionID: collection.ID, - Name: "Get All Posts", - Method: "GET", - URL: "https://jsonplaceholder.typicode.com/posts", - Headers: `{"Content-Type": "application/json"}`, - QueryParams: map[string]string{}, - RequestBody: "", - }, - { - CollectionID: collection.ID, - Name: "Get Single Post", - Method: "GET", - URL: "https://jsonplaceholder.typicode.com/posts/1", - Headers: `{"Content-Type": "application/json"}`, - QueryParams: map[string]string{}, - RequestBody: "", - }, - { - CollectionID: collection.ID, - Name: "Create Post", - Method: "POST", - URL: "https://jsonplaceholder.typicode.com/posts", - Headers: `{"Content-Type": "application/json"}`, - QueryParams: map[string]string{}, - RequestBody: `{"title": "My New Post", "body": "This is the content of my new post", "userId": 1}`, - }, - { - CollectionID: collection.ID, - Name: "Update Post", - Method: "PUT", - URL: "https://jsonplaceholder.typicode.com/posts/1", - Headers: `{"Content-Type": "application/json"}`, - QueryParams: map[string]string{}, - RequestBody: `{"id": 1, "title": "Updated Post", "body": "This post has been updated", "userId": 1}`, - }, - { - CollectionID: collection.ID, - Name: "Delete Post", - Method: "DELETE", - URL: "https://jsonplaceholder.typicode.com/posts/1", - Headers: `{"Content-Type": "application/json"}`, - QueryParams: map[string]string{}, - RequestBody: "", - }, - } - - return d.createEndpoints(ctx, endpoints) -} - -func (d *DemoGenerator) createReqresCollection(ctx context.Context) error { - collection, err := d.collectionsManager.Create(ctx, "ReqRes API") - if err != nil { - log.Error("failed to create ReqRes collection", "error", err) - return err - } - - endpoints := []endpoints.EndpointData{ - { - CollectionID: collection.ID, - Name: "List Users", - Method: "GET", - URL: "https://reqres.in/api/users", - Headers: `{"Content-Type": "application/json"}`, - QueryParams: map[string]string{"page": "2"}, - RequestBody: "", - }, - { - CollectionID: collection.ID, - Name: "Single User", - Method: "GET", - URL: "https://reqres.in/api/users/2", - Headers: `{"Content-Type": "application/json"}`, - QueryParams: map[string]string{}, - RequestBody: "", - }, - { - CollectionID: collection.ID, - Name: "Create User", - Method: "POST", - URL: "https://reqres.in/api/users", - Headers: `{"Content-Type": "application/json"}`, - QueryParams: map[string]string{}, - RequestBody: `{"name": "morpheus", "job": "leader"}`, - }, - { - CollectionID: collection.ID, - Name: "Login", - Method: "POST", - URL: "https://reqres.in/api/login", - Headers: `{"Content-Type": "application/json"}`, - QueryParams: map[string]string{}, - RequestBody: `{"email": "eve.holt@reqres.in", "password": "cityslicka"}`, - }, - } - - return d.createEndpoints(ctx, endpoints) -} - -func (d *DemoGenerator) createHTTPBinCollection(ctx context.Context) error { - collection, err := d.collectionsManager.Create(ctx, "HTTPBin Testing") - if err != nil { - log.Error("failed to create HTTPBin collection", "error", err) - return err - } - - endpoints := []endpoints.EndpointData{ - { - CollectionID: collection.ID, - Name: "Test GET", - Method: "GET", - URL: "https://httpbin.org/get", - Headers: `{"User-Agent": "Req-Terminal-Client/1.0"}`, - QueryParams: map[string]string{"test": "value", "demo": "true"}, - RequestBody: "", - }, - { - CollectionID: collection.ID, - Name: "Test POST JSON", - Method: "POST", - URL: "https://httpbin.org/post", - Headers: `{"Content-Type": "application/json", "User-Agent": "Req-Terminal-Client/1.0"}`, - QueryParams: map[string]string{}, - RequestBody: `{"message": "Hello from Req!", "timestamp": "2024-01-15T10:30:00Z", "data": {"key": "value"}}`, - }, - { - CollectionID: collection.ID, - Name: "Test Headers", - Method: "GET", - URL: "https://httpbin.org/headers", - Headers: `{"Authorization": "Bearer demo-token", "X-Custom-Header": "req-demo"}`, - QueryParams: map[string]string{}, - RequestBody: "", - }, - { - CollectionID: collection.ID, - Name: "Test Status Codes", - Method: "GET", - URL: "https://httpbin.org/status/200", - Headers: `{"Content-Type": "application/json"}`, - QueryParams: map[string]string{}, - RequestBody: "", - }, - } - - return d.createEndpoints(ctx, endpoints) -} - -func (d *DemoGenerator) createEndpoints(ctx context.Context, endpointData []endpoints.EndpointData) error { - for _, data := range endpointData { - _, err := d.endpointsManager.CreateEndpoint(ctx, data) - if err != nil { - log.Error("failed to create endpoint", "name", data.Name, "error", err) - return err - } - log.Debug("created demo endpoint", "name", data.Name, "method", data.Method, "url", data.URL) - } - return nil -} diff --git a/main.go b/main.go index 879eb69..5c082ca 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,6 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/maniac-en/req/internal/backend/collections" "github.com/maniac-en/req/internal/backend/database" - "github.com/maniac-en/req/internal/backend/demo" "github.com/maniac-en/req/internal/backend/endpoints" "github.com/maniac-en/req/internal/backend/history" "github.com/maniac-en/req/internal/backend/http" @@ -167,15 +166,6 @@ func run() error { getVersion(), ) - // populate dummy data for demo - demoGenerator := demo.NewDemoGenerator(collectionsManager, endpointsManager) - dummyDataCreated, err := demoGenerator.PopulateDummyData(context.Background()) - if err != nil { - log.Error("failed to populate dummy data", "error", err) - } else if dummyDataCreated { - // appContext.SetDummyDataCreated(true) - } - log.Info("application initialized", "components", []string{"database", "collections", "endpoints", "http", "history", "logging", "demo"}) log.Debug("configuration loaded", "collections_manager", collectionsManager != nil, "endpoints", endpointsManager != nil, "database", db != nil, "http_manager", httpManager != nil, "history_manager", historyManager != nil) log.Info("application started successfully") From c77d794c4ed1d4bb98c0fedf7021ae7484518deb Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Sat, 29 Aug 2026 22:02:26 +0200 Subject: [PATCH 18/46] feat: Add configuration loading and theme system - Implements config loading from user-specified TOML files. - Centralizes TUI styling by introducing a theme system (Dark, Light, Cyberpunk, etc.). - Refactors views to improve focus/blur handling and state consistency across components. - Updates core views and components to utilize the new themed styles and state patterns. --- go.mod | 1 + go.sum | 2 + internal/config/config.go | 49 +++++++++++++++ internal/config/types.go | 16 +++++ internal/tui/app/model.go | 17 ++---- .../components/OptionsProvider/component.go | 12 +++- internal/tui/styles/app-styles.go | 24 ++++++-- internal/tui/styles/collections-styles.go | 11 +++- internal/tui/styles/colors.go | 60 +++++++++++++++---- internal/tui/styles/functions.go | 2 +- internal/tui/styles/request-styles.go | 13 ++-- internal/tui/styles/theme.go | 33 ++++++++++ internal/tui/views/collections-view-helper.go | 2 +- internal/tui/views/collections-view.go | 31 ++++++---- internal/tui/views/endpoints-view-helper.go | 2 +- internal/tui/views/endpoints-view.go | 10 ++-- internal/tui/views/request-view.go | 16 +++-- internal/tui/views/types.go | 8 ++- main.go | 9 ++- 19 files changed, 255 insertions(+), 63 deletions(-) create mode 100644 internal/config/config.go create mode 100644 internal/config/types.go create mode 100644 internal/tui/styles/theme.go diff --git a/go.mod b/go.mod index 41f05a5..1b74c44 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/lipgloss v1.1.0 github.com/mattn/go-sqlite3 v1.14.29 + github.com/pelletier/go-toml/v2 v2.4.3 github.com/pressly/goose/v3 v3.24.3 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index 8dbf694..4090e98 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pressly/goose/v3 v3.24.3 h1:DSWWNwwggVUsYZ0X2VitiAa9sKuqtBfe+Jr9zFGwWlM= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..4ab7bfa --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,49 @@ +package config + +import ( + "errors" + "os" + "path" + + "github.com/maniac-en/req/internal/log" + "github.com/maniac-en/req/internal/tui/styles" + "github.com/pelletier/go-toml/v2" +) + +func LoadConfig() error { + configDir, err := os.UserConfigDir() + + if err != nil { + log.Error("Could not load user config dir") + return errors.New("Could not load user config dir") + } + path := path.Join(configDir, "req", "config.toml") + + byteData, err := os.ReadFile(path) + if err != nil { + log.Error("Could not load config") + initDefaults() + return nil + } + + var config Config + + toml.Unmarshal(byteData, &config) + + theme, ok := styles.ThemeMap[config.Theme.Name] + + if !ok { + theme = styles.ThemeMap["default"] + } + + styles.SetTheme(theme) + // styles.SetTheme() + + return nil +} + +func initDefaults() { + theme := styles.ThemeMap["default"] + log.Info("setting default config") + styles.SetTheme(theme) +} diff --git a/internal/config/types.go b/internal/config/types.go new file mode 100644 index 0000000..73255d4 --- /dev/null +++ b/internal/config/types.go @@ -0,0 +1,16 @@ +package config + +type Theme struct { +} + +type ThemeType struct { + Name string + Overrides Theme +} + +type ThemeOverrides struct { +} + +type Config struct { + Theme ThemeType `toml:"Theme"` +} diff --git a/internal/tui/app/model.go b/internal/tui/app/model.go index 77266d9..440d2d2 100644 --- a/internal/tui/app/model.go +++ b/internal/tui/app/model.go @@ -9,7 +9,6 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/log" - optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" "github.com/maniac-en/req/internal/tui/keybinds" "github.com/maniac-en/req/internal/tui/messages" "github.com/maniac-en/req/internal/tui/styles" @@ -60,16 +59,6 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } return a, tea.Batch(cmds...) - case messages.ChooseItem[optionsProvider.Option]: - switch msg.Source { - case "collections": - return a, func() tea.Msg { - return messages.NavigateToView{ - ViewName: string(Endpoints), - Data: msg.Item, - } - } - } case messages.NavigateToView: if msg.Data != nil { err := a.Views[ViewName(msg.ViewName)].SetState(msg.Data) @@ -84,8 +73,10 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.Views[a.focusedView].OnBlur() a.focusedView = ViewName(msg.ViewName) - a.Views[a.focusedView].OnFocus() - return a, nil + cmd = a.Views[a.focusedView].OnFocus() + cmds = append(cmds, cmd) + + return a, tea.Batch(cmds...) case messages.ShowError: log.Error("user operation failed", "error", msg.Message) diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index f3f13d8..aba4138 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -110,6 +110,16 @@ func (o OptionsProvider[T, U]) Update(msg tea.Msg) (OptionsProvider[T, U], tea.C return o, tea.Batch(cmds...) } +func (o *OptionsProvider[T, U]) UpdateState() tea.Cmd { + rawItems, err := o.getItems(context.Background()) + if err != nil { + // FIX: add err handling + } + + items := o.itemMapper(rawItems) + return o.list.SetItems(items) +} + func (o OptionsProvider[T, U]) View() string { if o.focused == textComponent { return lipgloss.JoinVertical(lipgloss.Left, o.list.View(), o.input.View()) @@ -118,11 +128,9 @@ func (o OptionsProvider[T, U]) View() string { } func (o *OptionsProvider[T, U]) OnFocus() { - o.list.SetDelegate(o.delegateGenner(true)) } func (o *OptionsProvider[T, U]) OnBlur() { - o.list.SetDelegate(o.delegateGenner(false)) } func (o OptionsProvider[T, U]) GetSelected() Option { diff --git a/internal/tui/styles/app-styles.go b/internal/tui/styles/app-styles.go index 365ef5d..bf89ec3 100644 --- a/internal/tui/styles/app-styles.go +++ b/internal/tui/styles/app-styles.go @@ -5,13 +5,25 @@ import ( ) var ( - footerNameStyle = lipgloss.NewStyle().Bold(true).Background(footerNameBG) - footerNameBGStyle = lipgloss.NewStyle().Background(footerNameBG).Padding(0, 3, 0) - FooterSegmentStyle = lipgloss.NewStyle().Background(footerSegmentBG).PaddingLeft(2).Foreground(footerSegmentFG) - FooterVersionStyle = lipgloss.NewStyle().Background(footerSegmentBG).AlignHorizontal(lipgloss.Right).PaddingRight(2).Foreground(footerSegmentFG) + footerNameStyle = lipgloss.NewStyle().Bold(true).Background(AppTheme.FooterNameBG) + footerNameBGStyle = lipgloss.NewStyle().Background(AppTheme.FooterNameBG).Padding(0, 3, 0) + FooterSegmentStyle = lipgloss.NewStyle().Background(AppTheme.FooterSegmentBG).PaddingLeft(2).Foreground(AppTheme.FooterSegmentFG) + FooterVersionStyle = lipgloss.NewStyle().Background(AppTheme.FooterSegmentBG).AlignHorizontal(lipgloss.Right).PaddingRight(2).Foreground(AppTheme.FooterSegmentFG) TabHeadingInactive = lipgloss.NewStyle().Width(25).AlignHorizontal(lipgloss.Center).Border(lipgloss.NormalBorder(), false, false, false, true) - TabHeadingActive = lipgloss.NewStyle().Background(accent).Foreground(headingForeground).Width(25).AlignHorizontal(lipgloss.Center).Border(lipgloss.NormalBorder(), false, false, false, true) + TabHeadingActive = lipgloss.NewStyle().Background(AppTheme.Accent).Foreground(AppTheme.HeadingForeground).Width(25).AlignHorizontal(lipgloss.Center).Border(lipgloss.NormalBorder(), false, false, false, true) HelpStyle = lipgloss.NewStyle().Padding(1, 0, 1, 2) - AppHelpStyle = lipgloss.NewStyle().Padding(1, 0).Foreground(helpFG) + AppHelpStyle = lipgloss.NewStyle().Padding(1, 0).Foreground(AppTheme.HelpFG) ErrorBarStyle = lipgloss.NewStyle().Background(lipgloss.Color("#FF0000")).Foreground(lipgloss.Color("#FFFFFF")).Padding(0, 1) ) + +func initAppStyles() { + footerNameStyle = lipgloss.NewStyle().Bold(true).Background(AppTheme.FooterNameBG) + footerNameBGStyle = lipgloss.NewStyle().Background(AppTheme.FooterNameBG).Padding(0, 3, 0) + FooterSegmentStyle = lipgloss.NewStyle().Background(AppTheme.FooterSegmentBG).PaddingLeft(2).Foreground(AppTheme.FooterSegmentFG) + FooterVersionStyle = lipgloss.NewStyle().Background(AppTheme.FooterSegmentBG).AlignHorizontal(lipgloss.Right).PaddingRight(2).Foreground(AppTheme.FooterSegmentFG) + TabHeadingInactive = lipgloss.NewStyle().Width(25).AlignHorizontal(lipgloss.Center).Border(lipgloss.NormalBorder(), false, false, false, true) + TabHeadingActive = lipgloss.NewStyle().Background(AppTheme.Accent).Foreground(AppTheme.HeadingForeground).Width(25).AlignHorizontal(lipgloss.Center).Border(lipgloss.NormalBorder(), false, false, false, true) + HelpStyle = lipgloss.NewStyle().Padding(1, 0, 1, 2) + AppHelpStyle = lipgloss.NewStyle().Padding(1, 0).Foreground(AppTheme.HelpFG) + ErrorBarStyle = lipgloss.NewStyle().Background(lipgloss.Color("#FF0000")).Foreground(lipgloss.Color("#FFFFFF")).Padding(0, 1) +} diff --git a/internal/tui/styles/collections-styles.go b/internal/tui/styles/collections-styles.go index a8d8614..daf234e 100644 --- a/internal/tui/styles/collections-styles.go +++ b/internal/tui/styles/collections-styles.go @@ -4,8 +4,17 @@ import "github.com/charmbracelet/lipgloss" var ( SelectedInactiveListStyle = lipgloss.NewStyle().PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true) - SelectedActiveListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) + SelectedActiveListStyle = lipgloss.NewStyle().Foreground(AppTheme.Accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(AppTheme.Accent) UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) // Grad = lipg ) + +func initCollectionStyles() { + + SelectedInactiveListStyle = lipgloss.NewStyle().PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true) + SelectedActiveListStyle = lipgloss.NewStyle().Foreground(AppTheme.Accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(AppTheme.Accent) + UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) + InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) + // Grad = lipg +} diff --git a/internal/tui/styles/colors.go b/internal/tui/styles/colors.go index 9cb8232..12f6f05 100644 --- a/internal/tui/styles/colors.go +++ b/internal/tui/styles/colors.go @@ -2,13 +2,53 @@ package styles import "github.com/charmbracelet/lipgloss" -var ( - footerNameBG = lipgloss.Color("#1a1a1a") - footerNameFGFrom = lipgloss.Color("#41A0AE") - footerNameFGTo = lipgloss.Color("#77F07F") - accent = lipgloss.Color("#77F07F") - headingForeground = lipgloss.Color("#000000") - footerSegmentBG = lipgloss.Color("#262626") - footerSegmentFG = lipgloss.Color("#656565") - helpFG = lipgloss.Color("#3C3C3C") -) +var ThemeMap = map[string]Theme{ + "default": DefaultTheme, + "cyberpunk": CyberpunkTheme, + "light-paper": LightPaperTheme, + "forest": ForestTheme, +} + +var DefaultTheme = Theme{ + FooterNameBG: lipgloss.Color("#1a1a1a"), + FooterNameFGFrom: lipgloss.Color("#41A0AE"), + FooterNameFGTo: lipgloss.Color("#77F07F"), + Accent: lipgloss.Color("#77F07F"), + HeadingForeground: lipgloss.Color("#000000"), + FooterSegmentBG: lipgloss.Color("#262626"), + FooterSegmentFG: lipgloss.Color("#656565"), + HelpFG: lipgloss.Color("#3C3C3C"), +} + +var LightPaperTheme = Theme{ + FooterNameBG: lipgloss.Color("#E5E5E0"), + FooterNameFGFrom: lipgloss.Color("#006680"), + FooterNameFGTo: lipgloss.Color("#2AA198"), + Accent: lipgloss.Color("#2AA198"), + HeadingForeground: lipgloss.Color("#073642"), + FooterSegmentBG: lipgloss.Color("#D3D3CD"), + FooterSegmentFG: lipgloss.Color("#586E75"), + HelpFG: lipgloss.Color("#93A1A1"), +} + +var ForestTheme = Theme{ + FooterNameBG: lipgloss.Color("#18201E"), + FooterNameFGFrom: lipgloss.Color("#40A02B"), // Rich forest green + FooterNameFGTo: lipgloss.Color("#179299"), // Deep teal + Accent: lipgloss.Color("#40A02B"), // Darker green with much higher contrast + HeadingForeground: lipgloss.Color("#E6E9EF"), + FooterSegmentBG: lipgloss.Color("#25322F"), + FooterSegmentFG: lipgloss.Color("#97B6AC"), + HelpFG: lipgloss.Color("#4F6C64"), +} + +var CyberpunkTheme = Theme{ + FooterNameBG: lipgloss.Color("#181425"), + FooterNameFGFrom: lipgloss.Color("#FF2A6D"), + FooterNameFGTo: lipgloss.Color("#05D9E8"), + Accent: lipgloss.Color("#05D9E8"), + HeadingForeground: lipgloss.Color("#FFFFFF"), + FooterSegmentBG: lipgloss.Color("#241734"), + FooterSegmentFG: lipgloss.Color("#8A78A5"), + HelpFG: lipgloss.Color("#62447D"), +} diff --git a/internal/tui/styles/functions.go b/internal/tui/styles/functions.go index 422250b..d031438 100644 --- a/internal/tui/styles/functions.go +++ b/internal/tui/styles/functions.go @@ -39,5 +39,5 @@ func hexToRGB(hex string) (int, int, int) { } func ApplyGradientToFooter(text string) string { - return gradientText("REQ", footerNameFGFrom, footerNameFGTo, footerNameStyle, footerNameBGStyle) + return gradientText("REQ", AppTheme.FooterNameFGFrom, AppTheme.FooterNameFGTo, footerNameStyle, footerNameBGStyle) } diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index d712c2d..7c6c7af 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -6,8 +6,8 @@ func RequestLayout(height, width int) func(...string) string { return lipgloss.NewStyle(). Height(height). Width(width). - Margin(0, 0, 0, 5). - Padding(1, 0, 0, 10). + Margin(0, 0, 0, 1). + Padding(1, 0, 0, 0). Render } @@ -15,7 +15,7 @@ func UrlInputStyle(active bool) lipgloss.Style { var color lipgloss.Color if active { - color = accent + color = AppTheme.Accent } return lipgloss.NewStyle(). @@ -30,6 +30,11 @@ func ResponseStyle(height, width int) func(...string) string { } var ( - ActiveRequestItem = lipgloss.NewStyle().Foreground(accent) + ActiveRequestItem = lipgloss.NewStyle().Foreground(AppTheme.Accent) InactiveRequestItem = lipgloss.NewStyle() ) + +func initRequestStyles() { + ActiveRequestItem = lipgloss.NewStyle().Foreground(AppTheme.Accent) + InactiveRequestItem = lipgloss.NewStyle() +} diff --git a/internal/tui/styles/theme.go b/internal/tui/styles/theme.go new file mode 100644 index 0000000..649242a --- /dev/null +++ b/internal/tui/styles/theme.go @@ -0,0 +1,33 @@ +package styles + +import ( + "github.com/charmbracelet/lipgloss" +) + +type Theme struct { + FooterNameBG lipgloss.Color + FooterNameFGFrom lipgloss.Color + FooterNameFGTo lipgloss.Color + Accent lipgloss.Color + HeadingForeground lipgloss.Color + FooterSegmentBG lipgloss.Color + FooterSegmentFG lipgloss.Color + HelpFG lipgloss.Color +} + +var ( + AppTheme Theme +) + +func init() { + SetTheme(DefaultTheme) +} + +func SetTheme(newTheme Theme) { + AppTheme = newTheme + + // reset styles + initRequestStyles() + initCollectionStyles() + initAppStyles() +} diff --git a/internal/tui/views/collections-view-helper.go b/internal/tui/views/collections-view-helper.go index 9c7b4e7..06a7a46 100644 --- a/internal/tui/views/collections-view-helper.go +++ b/internal/tui/views/collections-view-helper.go @@ -15,7 +15,7 @@ func createCollectionDelegate(active bool) list.ItemDelegate { d.Styles.SelectedTitle = styles.SelectedInactiveListStyle } - d.Styles.SelectedDesc = styles.UnselectedListStyle + d.Styles.SelectedDesc = styles.SelectedActiveListStyle return d } diff --git a/internal/tui/views/collections-view.go b/internal/tui/views/collections-view.go index b07e3f8..0c4399d 100644 --- a/internal/tui/views/collections-view.go +++ b/internal/tui/views/collections-view.go @@ -27,23 +27,23 @@ type CollectionsView struct { order int } -func (c CollectionsView) Init() tea.Cmd { +func (c *CollectionsView) Init() tea.Cmd { return nil } -func (c CollectionsView) Name() string { +func (c *CollectionsView) Name() string { return "Collections" } -func (c CollectionsView) Help() []key.Binding { +func (c *CollectionsView) Help() []key.Binding { return c.list.Help() } -func (c CollectionsView) GetFooterSegment() string { +func (c *CollectionsView) GetFooterSegment() string { return c.list.GetSelected().Title() } -func (c CollectionsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { +func (c *CollectionsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { var cmd tea.Cmd var cmds []tea.Cmd switch msg := msg.(type) { @@ -70,6 +70,15 @@ func (c CollectionsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case messages.DeleteItem: c.manager.Delete(context.Background(), msg.ItemID) c.list.RefreshItems() + case messages.ChooseItem[optionsProvider.Option]: + c.list.OnBlur() + return c, func() tea.Msg { + return messages.NavigateToView{ + ViewName: string(Endpoints), + Target: MainModel, + Data: msg.Item, + } + } } c.list, cmd = c.list.Update(msg) @@ -78,23 +87,23 @@ func (c CollectionsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return c, tea.Batch(cmds...) } -func (c CollectionsView) View() string { +func (c *CollectionsView) View() string { return c.list.View() } -func (c CollectionsView) SetState(items ...any) error { +func (c *CollectionsView) SetState(items ...any) error { return errors.New("This view does not implement set state") } -func (c CollectionsView) Order() int { +func (c *CollectionsView) Order() int { return c.order } -func (c CollectionsView) OnFocus() { - +func (c *CollectionsView) OnFocus() tea.Cmd { + return c.list.UpdateState() } -func (c CollectionsView) OnBlur() { +func (c *CollectionsView) OnBlur() { } diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go index d44d704..9c8a5ac 100644 --- a/internal/tui/views/endpoints-view-helper.go +++ b/internal/tui/views/endpoints-view-helper.go @@ -13,7 +13,7 @@ func createEndpointsDelegate(active bool) list.ItemDelegate { d.Styles.SelectedTitle = styles.SelectedInactiveListStyle } - d.Styles.SelectedDesc = styles.UnselectedListStyle + d.Styles.SelectedDesc = styles.SelectedActiveListStyle return d } diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 43fe5b9..a8e0c69 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -78,7 +78,10 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return messages.NavigateToView{ ViewName: "request", Target: MainModel, - Data: msg.Item.ID, + Data: EndpointData{ + Collection: e.collection, + EndpointID: msg.Item.ID, + }, } } case messages.NavigateToView: @@ -88,7 +91,6 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { // FIX: idek // e.requestView.OnBlur() e.focused = listView - e.list.OnFocus() case tea.KeyMsg: switch { @@ -119,8 +121,8 @@ func (e *EndpointsView) View() string { return e.list.View() } -func (e *EndpointsView) OnFocus() { - +func (e *EndpointsView) OnFocus() tea.Cmd { + return e.list.UpdateState() } func (e *EndpointsView) SetState(items ...any) error { diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index c7dcf6d..d71b3cb 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -15,6 +15,7 @@ import ( "github.com/maniac-en/req/internal/backend/http" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" + optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" viewport "github.com/maniac-en/req/internal/tui/components/ViewPort" "github.com/maniac-en/req/internal/tui/keybinds" @@ -51,6 +52,7 @@ type RequestView struct { order int update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) endpoint endpoints.EndpointEntity + collection optionsProvider.Option } func (r *RequestView) Init() tea.Cmd { @@ -80,7 +82,7 @@ func (r *RequestView) Help() []key.Binding { } func (r *RequestView) GetFooterSegment() string { - return "" + return fmt.Sprintf("%s/%s", r.collection.Name, r.endpoint.Name) } func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { @@ -112,7 +114,7 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return r, func() tea.Msg { return messages.NavigateToView{ ViewName: Endpoints, - Target: Endpoints, + Target: MainModel, } } case key.Matches(msg, keybinds.Keys.Next): @@ -179,7 +181,7 @@ func (r *RequestView) shift(next bool) { if next { r.index = (r.index + 1) % len(componentList) } else { - r.index = (r.index - 1) % len(componentList) + r.index = (r.index - 1 + len(componentList)) % len(componentList) } r.endpoint = r.components[r.focused].UpdateState(r.endpoint) r.components[r.focused].OnBlur() @@ -221,8 +223,9 @@ func (r *RequestView) View() string { func (r *RequestView) SetState(items ...any) error { if len(items) == 1 { - if id, ok := items[0].(int64); ok { - ep, err := r.epManager.Read(context.Background(), id) + if data, ok := items[0].(EndpointData); ok { + ep, err := r.epManager.Read(context.Background(), data.EndpointID) + r.collection = data.Collection if err != nil { // FIX: do something over here } @@ -240,8 +243,9 @@ func (r *RequestView) Order() int { return r.order } -func (r *RequestView) OnFocus() { +func (r *RequestView) OnFocus() tea.Cmd { r.components[r.focused].OnFocus() + return nil } func (r *RequestView) OnBlur() { diff --git a/internal/tui/views/types.go b/internal/tui/views/types.go index 465b14d..22a30ff 100644 --- a/internal/tui/views/types.go +++ b/internal/tui/views/types.go @@ -3,6 +3,7 @@ package views import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" + optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" ) type ViewInterface interface { @@ -14,6 +15,11 @@ type ViewInterface interface { View() string Order() int SetState(...any) error - OnFocus() + OnFocus() tea.Cmd OnBlur() } + +type EndpointData struct { + EndpointID int64 + Collection optionsProvider.Option +} diff --git a/main.go b/main.go index 5c082ca..cc595a1 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ import ( "github.com/maniac-en/req/internal/backend/endpoints" "github.com/maniac-en/req/internal/backend/history" "github.com/maniac-en/req/internal/backend/http" + "github.com/maniac-en/req/internal/config" "github.com/maniac-en/req/internal/log" "github.com/maniac-en/req/internal/tui/app" _ "github.com/mattn/go-sqlite3" @@ -145,7 +146,7 @@ func run() error { // run database migrations if err := runMigrations(); err != nil { - return fmt.Errorf("failed to run migrations", "error", err) + return fmt.Errorf("failed to run migrations: %s", err) } // create database client and managers @@ -171,8 +172,12 @@ func run() error { log.Info("application started successfully") // Entry point for UI + err := config.LoadConfig() + if err != nil { + return fmt.Errorf("fatal error: %w", err) + } program := tea.NewProgram(app.NewAppModel(appContext), tea.WithAltScreen()) - if _, err := program.Run(); err != nil { + if _, err = program.Run(); err != nil { return fmt.Errorf("fatal error: %w", err) } return nil From d3586b866691ff5d1fd549abb9285f50b0a02186 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Sat, 29 Aug 2026 22:16:25 +0200 Subject: [PATCH 19/46] refactor: Update TUI styling and state handling - Adjusted padding and margin settings in style definitions for improved layout consistency. - Updated state setting logic in RequestView to correctly use the viewports state management. --- internal/tui/styles/request-styles.go | 5 ++--- internal/tui/views/request-view.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index 7c6c7af..c668d2c 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -6,8 +6,7 @@ func RequestLayout(height, width int) func(...string) string { return lipgloss.NewStyle(). Height(height). Width(width). - Margin(0, 0, 0, 1). - Padding(1, 0, 0, 0). + Padding(1, 0, 0, 1). Render } @@ -26,7 +25,7 @@ func UrlInputStyle(active bool) lipgloss.Style { } func ResponseStyle(height, width int) func(...string) string { - return lipgloss.NewStyle().Margin(0, 0, 0, 5).Padding(1, 0, 0, 5).Height(height).Width(width).Render + return lipgloss.NewStyle().Padding(1, 0, 0, 1).Height(height).Width(width).Render } var ( diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index d71b3cb..b1468b1 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -102,7 +102,7 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { if msg.Err != nil { // TODO: do something here idek } - r.SetState(msg.Data) + r.viewport.SetState(msg.Data) r.loading = false case tea.KeyMsg: switch { From 1dd0ccf804ebfc6e244612e87a506e66af16f9d7 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Wed, 5 Nov 2025 17:36:50 +0100 Subject: [PATCH 20/46] fixed selection source --- internal/tui/views/endpoints-view.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 91f1d19..d70f1c9 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -132,7 +132,7 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin config.GetItemsFunc = epListFunc config.ItemMapper = itemMapperEp config.AdditionalKeymaps = keybinds - config.Source = "collections" + config.Source = "endpoints" view.list = optionsProvider.NewOptionsProvider(config) From 64231e82f35469d74107918b44b09199a667c2ab Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 7 Nov 2025 00:13:47 +0100 Subject: [PATCH 21/46] new delegate created for the endpoints page --- internal/tui/views/collections-view-helper.go | 6 +-- internal/tui/views/collections-view.go | 2 +- internal/tui/views/endpoints-view-helper.go | 45 +++++++++++++++++++ internal/tui/views/endpoints-view.go | 2 +- 4 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 internal/tui/views/endpoints-view-helper.go diff --git a/internal/tui/views/collections-view-helper.go b/internal/tui/views/collections-view-helper.go index 7174dc9..0dbbefc 100644 --- a/internal/tui/views/collections-view-helper.go +++ b/internal/tui/views/collections-view-helper.go @@ -7,7 +7,7 @@ import ( "github.com/yashranjan1/relay/internal/tui/styles" ) -func createDelegate() list.DefaultDelegate { +func createCollectionDelegate() list.ItemDelegate { d := list.NewDefaultDelegate() d.Styles.SelectedTitle = styles.SelectedListStyle @@ -16,14 +16,14 @@ func createDelegate() list.DefaultDelegate { return d } -func defaultListConfig[T, U any](binds *keybinds.ListKeyMap) *optionsProvider.ListConfig[T, U] { +func defaultListConfig[T, U any](binds *keybinds.ListKeyMap, delegateCreator func() list.ItemDelegate) *optionsProvider.ListConfig[T, U] { config := optionsProvider.ListConfig[T, U]{ ShowPagination: true, ShowStatusBar: false, ShowHelp: false, ShowTitle: false, FilteringEnabled: true, - Delegate: createDelegate(), + Delegate: delegateCreator(), KeyMap: list.KeyMap{ CursorUp: binds.CursorUp, CursorDown: binds.CursorDown, diff --git a/internal/tui/views/collections-view.go b/internal/tui/views/collections-view.go index af082f6..64ffe4a 100644 --- a/internal/tui/views/collections-view.go +++ b/internal/tui/views/collections-view.go @@ -132,7 +132,7 @@ func itemMapper(items []collections.CollectionEntity, endpointsManager *endpoint func NewCollectionsView(collManager *collections.CollectionsManager, endpointsManager *endpoints.EndpointsManager, order int) *CollectionsView { keybinds := keybinds.NewListKeyMap() - config := defaultListConfig[collections.CollectionEntity, string](keybinds) + config := defaultListConfig[collections.CollectionEntity, string](keybinds, createCollectionDelegate) config.GetItemsFunc = collManager.List config.ItemMapper = func(items []collections.CollectionEntity) []list.Item { diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go new file mode 100644 index 0000000..0e856aa --- /dev/null +++ b/internal/tui/views/endpoints-view-helper.go @@ -0,0 +1,45 @@ +package views + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/maniac-en/req/internal/tui/components/OptionsProvider" +) + +func createEndpointsDelegate() list.ItemDelegate { + d := epItemDelegate{} + + // d.Styles.SelectedTitle = styles.SelectedListStyle + // d.Styles.SelectedDesc = styles.SelectedListStyle + + return d +} + +type epItemDelegate struct{} + +func (e epItemDelegate) Height() int { return 1 } +func (e epItemDelegate) Spacing() int { return 1 } +func (e epItemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } +func (e epItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + item, ok := listItem.(optionsProvider.Option) + + if !ok { + return + } + + str := fmt.Sprintf("%s %s", item.Subtext, item.Name) + + fn := lipgloss.NewStyle().Render + if index == m.Index() { + fn = func(s ...string) string { + return lipgloss.NewStyle().Render("> " + strings.Join(s, " ")) + } + } + + fmt.Fprint(w, fn(str)) +} diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index d70f1c9..0f654cf 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -123,7 +123,7 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin } keybinds := keybinds.NewListKeyMap() - config := defaultListConfig[endpoints.EndpointEntity, database.Endpoint](keybinds) + config := defaultListConfig[endpoints.EndpointEntity, database.Endpoint](keybinds, createEndpointsDelegate) epListFunc := func(ctx context.Context) ([]endpoints.EndpointEntity, error) { return epManager.ListByCollection(ctx, view.collection.ID) From 11aadcc7b4e44af10818b291548fea55f6e32f03 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 7 Nov 2025 14:45:13 +0100 Subject: [PATCH 22/46] page up/down binds removed, keybind wording made better, breadcrumbs fixed, ep list rendering changed as discussed in issue #69 --- internal/tui/keybinds/keys.go | 10 +++++----- internal/tui/styles/collections-styles.go | 5 +++-- internal/tui/views/endpoints-view-helper.go | 14 +++++++++----- internal/tui/views/endpoints-view.go | 2 +- 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/internal/tui/keybinds/keys.go b/internal/tui/keybinds/keys.go index d04f267..f731a98 100644 --- a/internal/tui/keybinds/keys.go +++ b/internal/tui/keybinds/keys.go @@ -36,12 +36,12 @@ var Keys = Keymaps{ key.WithHelp("↓/j", "down"), ), PrevPage: key.NewBinding( - key.WithKeys("left", "h", "pgup"), - key.WithHelp("←/h/pgup", "prev page"), + key.WithKeys("left", "h"), + key.WithHelp("←/h", "prev page"), ), NextPage: key.NewBinding( key.WithKeys("right", "l", "pgdown"), - key.WithHelp("→/l/pgdn", "next page"), + key.WithHelp("→/l", "next page"), ), Filter: key.NewBinding( key.WithKeys("/"), @@ -61,11 +61,11 @@ var Keys = Keymaps{ ), InsertItem: key.NewBinding( key.WithKeys("a"), - key.WithHelp("a", "add item"), + key.WithHelp("a", "add"), ), EditItem: key.NewBinding( key.WithKeys("e"), - key.WithHelp("e", "edit item"), + key.WithHelp("e", "edit"), ), Choose: key.NewBinding( key.WithKeys("enter"), diff --git a/internal/tui/styles/collections-styles.go b/internal/tui/styles/collections-styles.go index 600e885..f044adb 100644 --- a/internal/tui/styles/collections-styles.go +++ b/internal/tui/styles/collections-styles.go @@ -3,6 +3,7 @@ package styles import "github.com/charmbracelet/lipgloss" var ( - SelectedListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) - InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) + SelectedListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) + UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) + InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) ) diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go index 0e856aa..1ca21ba 100644 --- a/internal/tui/views/endpoints-view-helper.go +++ b/internal/tui/views/endpoints-view-helper.go @@ -9,18 +9,22 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/tui/components/OptionsProvider" + "github.com/maniac-en/req/internal/tui/styles" ) func createEndpointsDelegate() list.ItemDelegate { d := epItemDelegate{} - // d.Styles.SelectedTitle = styles.SelectedListStyle - // d.Styles.SelectedDesc = styles.SelectedListStyle + d.SelectedTitleStyle = styles.SelectedListStyle + d.UnselectedTitleStyle = styles.UnselectedListStyle return d } -type epItemDelegate struct{} +type epItemDelegate struct { + SelectedTitleStyle lipgloss.Style + UnselectedTitleStyle lipgloss.Style +} func (e epItemDelegate) Height() int { return 1 } func (e epItemDelegate) Spacing() int { return 1 } @@ -34,10 +38,10 @@ func (e epItemDelegate) Render(w io.Writer, m list.Model, index int, listItem li str := fmt.Sprintf("%s %s", item.Subtext, item.Name) - fn := lipgloss.NewStyle().Render + fn := e.UnselectedTitleStyle.Render if index == m.Index() { fn = func(s ...string) string { - return lipgloss.NewStyle().Render("> " + strings.Join(s, " ")) + return e.SelectedTitleStyle.Render(strings.Join(s, " ")) } } diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 0f654cf..d5fd3eb 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -37,7 +37,7 @@ func (e *EndpointsView) Help() []key.Binding { } func (e *EndpointsView) GetFooterSegment() string { - return fmt.Sprintf("%s/", e.collection.Name) + return fmt.Sprintf("%s/%s", e.collection.Name, e.list.GetSelected().Name) } func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { From 4cf7bf1da5fc6db5ae0ecf29bc9709d1f00a2573 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 7 Nov 2025 18:38:35 +0100 Subject: [PATCH 23/46] initing the method picker --- .../tui/components/MethodPicker/component.go | 65 ++++++++++++++++ internal/tui/views/endpoints-view.go | 33 +++++++-- internal/tui/views/request-view.go | 74 +++++++++++++++++++ 3 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 internal/tui/components/MethodPicker/component.go create mode 100644 internal/tui/views/request-view.go diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go new file mode 100644 index 0000000..805f91b --- /dev/null +++ b/internal/tui/components/MethodPicker/component.go @@ -0,0 +1,65 @@ +package methodpicker + +import ( + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/tui/keybinds" +) + +type MethodPicker[T any] struct { + list list.Model + onSelectAction tea.Msg + keys *keybinds.ListKeyMap + width int + height int + itemMapper func([]T) []list.Item +} + +type MethodOption struct { + Name string + Type string +} + +func (m MethodOption) Title() string { return m.Name } +func (m MethodOption) Description() string { return m.Name } +func (m MethodOption) Value() string { return m.Type } +func (m MethodOption) FilterValue() string { return "" } + +func (m MethodPicker[T]) Init() tea.Cmd { + return nil +} + +func (m MethodPicker[T]) Update(msg tea.Msg) (MethodPicker[T], tea.Cmd) { + return m, nil +} + +func (m MethodPicker[T]) View() string { + return "hello" +} + +func (m *MethodPicker[T]) OnFocus() { +} + +func (m MethodPicker[T]) OnBlur() { + +} + +func (m MethodPicker[T]) GetSelected() MethodOption { + return m.list.SelectedItem().(MethodOption) +} + +func (m *MethodPicker[T]) Help() []key.Binding { + return []key.Binding{ + m.keys.NextPage, + m.keys.PrevPage, + m.keys.CursorUp, + m.keys.CursorDown, + m.keys.Accept, + } +} + +func NewMethodPicker[T any]() MethodPicker[T] { + + return MethodPicker[T]{} +} diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index d5fd3eb..bc0433f 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -8,6 +8,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/yashranjan1/relay/internal/backend/database" "github.com/yashranjan1/relay/internal/backend/endpoints" optionsProvider "github.com/yashranjan1/relay/internal/tui/components/OptionsProvider" @@ -15,13 +16,22 @@ import ( "github.com/yashranjan1/relay/internal/tui/messages" ) +type epFocused string + +const ( + listView = "listview" + requestView = "requestview" +) + type EndpointsView struct { - height int - collection optionsProvider.Option - width int - order int - list optionsProvider.OptionsProvider[endpoints.EndpointEntity, database.Endpoint] - manager *endpoints.EndpointsManager + height int + focused epFocused + collection optionsProvider.Option + width int + order int + list optionsProvider.OptionsProvider[endpoints.EndpointEntity, database.Endpoint] + requestView ViewInterface + manager *endpoints.EndpointsManager } func (e *EndpointsView) Init() tea.Cmd { @@ -61,15 +71,20 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.manager.Delete(context.Background(), msg.ItemID) e.list.RefreshItems() } + switch e.focused { + case listView: + e.list, cmd = e.list.Update(msg) + case requestView: + e.requestView, cmd = e.requestView.Update(msg) + } - e.list, cmd = e.list.Update(msg) cmds = append(cmds, cmd) return e, tea.Batch(cmds...) } func (e *EndpointsView) View() string { - return e.list.View() + return lipgloss.JoinHorizontal(lipgloss.Center, e.list.View(), e.requestView.View()) } func (e *EndpointsView) OnFocus() { @@ -135,6 +150,8 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin config.Source = "endpoints" view.list = optionsProvider.NewOptionsProvider(config) + view.requestView = NewRequestView() + view.focused = listView return view } diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go new file mode 100644 index 0000000..f9f2ec9 --- /dev/null +++ b/internal/tui/views/request-view.go @@ -0,0 +1,74 @@ +package views + +import ( + "errors" + + "github.com/charmbracelet/bubbles/help" + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/backend/database" + methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" + "github.com/maniac-en/req/internal/tui/keybinds" +) + +type RequestView struct { + width int + height int + help help.Model + keys *keybinds.ListKeyMap + methodPicker methodpicker.MethodPicker[string] + order int + endpoint database.Endpoint +} + +func (r RequestView) Init() tea.Cmd { + return nil +} + +func (r RequestView) Name() string { + return "Request" +} + +func (r RequestView) Help() []key.Binding { + return []key.Binding{} +} + +func (r RequestView) GetFooterSegment() string { + return "" +} + +func (r RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { + var cmd tea.Cmd + + return r, cmd +} + +func (r RequestView) View() string { + return r.methodPicker.View() +} + +func (r RequestView) SetState(items ...any) error { + if len(items) != 1 { + return errors.New("Incorrect amount of fields supplied") + } + r.SetState(items[0]) + return nil +} + +func (r RequestView) Order() int { + return r.order +} + +func (r RequestView) OnFocus() { + +} + +func (r RequestView) OnBlur() { + +} + +func NewRequestView() *RequestView { + return &RequestView{ + methodPicker: methodpicker.NewMethodPicker[string](), + } +} From ffed6f1448713c0c0a32bc539a7f91c6aa026c3c Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Sat, 8 Nov 2025 17:39:27 +0100 Subject: [PATCH 24/46] basics for the method picker implemented --- .../tui/components/MethodPicker/component.go | 30 ++++++++++-- .../tui/components/MethodPicker/config.go | 27 +++++++++++ internal/tui/styles/request-styles.go | 12 +++++ internal/tui/views/endpoints-view.go | 4 +- internal/tui/views/request-view.go | 48 +++++++++++++++++-- 5 files changed, 112 insertions(+), 9 deletions(-) create mode 100644 internal/tui/components/MethodPicker/config.go create mode 100644 internal/tui/styles/request-styles.go diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go index 805f91b..506e6b3 100644 --- a/internal/tui/components/MethodPicker/component.go +++ b/internal/tui/components/MethodPicker/component.go @@ -24,18 +24,22 @@ type MethodOption struct { func (m MethodOption) Title() string { return m.Name } func (m MethodOption) Description() string { return m.Name } func (m MethodOption) Value() string { return m.Type } -func (m MethodOption) FilterValue() string { return "" } +func (m MethodOption) FilterValue() string { return m.Name } func (m MethodPicker[T]) Init() tea.Cmd { return nil } func (m MethodPicker[T]) Update(msg tea.Msg) (MethodPicker[T], tea.Cmd) { - return m, nil + var cmd tea.Cmd + + m.list, cmd = m.list.Update(msg) + + return m, cmd } func (m MethodPicker[T]) View() string { - return "hello" + return m.list.View() } func (m *MethodPicker[T]) OnFocus() { @@ -59,7 +63,23 @@ func (m *MethodPicker[T]) Help() []key.Binding { } } -func NewMethodPicker[T any]() MethodPicker[T] { +func NewMethodPicker[T any](config MethodPickerConfig[T]) MethodPicker[T] { + method := MethodPicker[T]{} + + picker := list.New( + config.ItemMapper(config.Items), + config.Delegate, + config.Width, + config.Height, + ) + + picker.SetFilteringEnabled(config.FilteringEnabled) + picker.SetShowStatusBar(config.ShowStatusBar) + picker.SetShowPagination(config.ShowPagination) + picker.SetShowHelp(config.ShowHelp) + picker.SetShowTitle(config.ShowTitle) + + method.list = picker - return MethodPicker[T]{} + return method } diff --git a/internal/tui/components/MethodPicker/config.go b/internal/tui/components/MethodPicker/config.go new file mode 100644 index 0000000..689a6d5 --- /dev/null +++ b/internal/tui/components/MethodPicker/config.go @@ -0,0 +1,27 @@ +package methodpicker + +import ( + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/tui/keybinds" +) + +type MethodPickerConfig[T any] struct { + Items []T + ItemMapper func([]T) []list.Item + FilteringEnabled bool + + Delegate list.ItemDelegate + KeyMap list.KeyMap + AdditionalKeymaps *keybinds.ListKeyMap + + OnSelectAction tea.Msg + + ShowPagination bool + ShowStatusBar bool + ShowHelp bool + ShowTitle bool + Width, Height int +} + +func config() {} diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go new file mode 100644 index 0000000..c528a1c --- /dev/null +++ b/internal/tui/styles/request-styles.go @@ -0,0 +1,12 @@ +package styles + +import "github.com/charmbracelet/lipgloss" + +func RequestLayout(height, width int) func(...string) string { + return lipgloss.NewStyle(). + Height(height). + Width(width). + Padding(1, 0, 0, 1). + BorderLeft(true). + Render +} diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index bc0433f..7f32fa9 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -70,6 +70,8 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case messages.DeleteItem: e.manager.Delete(context.Background(), msg.ItemID) e.list.RefreshItems() + case messages.ChooseItem[optionsProvider.Option]: + e.focused = requestView } switch e.focused { case listView: @@ -84,7 +86,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { } func (e *EndpointsView) View() string { - return lipgloss.JoinHorizontal(lipgloss.Center, e.list.View(), e.requestView.View()) + return lipgloss.JoinHorizontal(lipgloss.Top, e.list.View(), e.requestView.View()) } func (e *EndpointsView) OnFocus() { diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index f9f2ec9..68c84b4 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -5,10 +5,12 @@ import ( "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/maniac-en/req/internal/backend/database" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" "github.com/maniac-en/req/internal/tui/keybinds" + "github.com/maniac-en/req/internal/tui/styles" ) type RequestView struct { @@ -39,12 +41,21 @@ func (r RequestView) GetFooterSegment() string { func (r RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { var cmd tea.Cmd + var cmds []tea.Cmd + switch msg := msg.(type) { + case tea.WindowSizeMsg: + r.height = msg.Height + r.width = msg.Width + } + + r.methodPicker, cmd = r.methodPicker.Update(msg) + cmds = append(cmds, cmd) - return r, cmd + return r, tea.Batch(cmds...) } func (r RequestView) View() string { - return r.methodPicker.View() + return styles.RequestLayout(10, 100)(r.methodPicker.View()) } func (r RequestView) SetState(items ...any) error { @@ -67,8 +78,39 @@ func (r RequestView) OnBlur() { } +func methodItemMapper(items []string) []list.Item { + listItems := make([]list.Item, len(items)) + for index, item := range items { + listItems[index] = methodpicker.MethodOption{ + Name: item, + Type: item, + } + } + return listItems +} + func NewRequestView() *RequestView { + methods := []string{ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + } + + config := methodpicker.MethodPickerConfig[string]{ + Items: methods, + ItemMapper: methodItemMapper, + FilteringEnabled: false, + ShowPagination: false, + ShowStatusBar: false, + Delegate: list.DefaultDelegate{}, + ShowHelp: false, + ShowTitle: false, + Width: 30, + Height: 1, + } return &RequestView{ - methodPicker: methodpicker.NewMethodPicker[string](), + methodPicker: methodpicker.NewMethodPicker[string](config), } } From 0a72ce8ab630bff906accb34c244d01d7f90fb55 Mon Sep 17 00:00:00 2001 From: Yash Ranjan Date: Wed, 25 Feb 2026 15:04:28 +0100 Subject: [PATCH 25/46] fixed some navigation issues --- internal/tui/app/model.go | 15 ++---- .../tui/components/MethodPicker/component.go | 9 ++-- .../tui/components/MethodPicker/config.go | 2 +- .../components/OptionsProvider/component.go | 23 ++++---- .../tui/components/OptionsProvider/config.go | 2 +- internal/tui/messages/messages.go | 1 + internal/tui/styles/collections-styles.go | 7 +-- internal/tui/styles/request-styles.go | 4 ++ internal/tui/views/collections-view-helper.go | 14 +++-- internal/tui/views/endpoints-view-helper.go | 8 ++- internal/tui/views/endpoints-view.go | 26 +++++++++ internal/tui/views/request-view-helper.go | 53 +++++++++++++++++++ internal/tui/views/request-view.go | 40 +++++++++----- internal/tui/views/view-reference.go | 8 +++ main.go | 1 + 15 files changed, 164 insertions(+), 49 deletions(-) create mode 100644 internal/tui/views/request-view-helper.go create mode 100644 internal/tui/views/view-reference.go diff --git a/internal/tui/app/model.go b/internal/tui/app/model.go index d5c856d..511484c 100644 --- a/internal/tui/app/model.go +++ b/internal/tui/app/model.go @@ -67,16 +67,18 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } } case messages.NavigateToView: - a.Views[a.focusedView].OnBlur() - if msg.Data != nil { err := a.Views[ViewName(msg.ViewName)].SetState(msg.Data) if err != nil { log.Error("failed to set view state during navigation", "target_view", msg.ViewName, "error", err) return a, nil } + } else if msg.Target != views.MainModel { + break } + a.Views[a.focusedView].OnBlur() + a.focusedView = ViewName(msg.ViewName) a.Views[a.focusedView].OnFocus() return a, nil @@ -89,15 +91,6 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch { case key.Matches(msg, keybinds.Keys.Quit): return a, tea.Quit - case key.Matches(msg, keybinds.Keys.Back): - if a.focusedView == Endpoints { - return a, func() tea.Msg { - return messages.NavigateToView{ - ViewName: string(Collections), - Data: nil, - } - } - } } } diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go index 506e6b3..46718b7 100644 --- a/internal/tui/components/MethodPicker/component.go +++ b/internal/tui/components/MethodPicker/component.go @@ -10,6 +10,7 @@ import ( type MethodPicker[T any] struct { list list.Model onSelectAction tea.Msg + delegateGenner func(bool) list.ItemDelegate keys *keybinds.ListKeyMap width int height int @@ -43,10 +44,11 @@ func (m MethodPicker[T]) View() string { } func (m *MethodPicker[T]) OnFocus() { + m.list.SetDelegate(m.delegateGenner(true)) } -func (m MethodPicker[T]) OnBlur() { - +func (m *MethodPicker[T]) OnBlur() { + m.list.SetDelegate(m.delegateGenner(false)) } func (m MethodPicker[T]) GetSelected() MethodOption { @@ -68,7 +70,7 @@ func NewMethodPicker[T any](config MethodPickerConfig[T]) MethodPicker[T] { picker := list.New( config.ItemMapper(config.Items), - config.Delegate, + config.Delegate(false), config.Width, config.Height, ) @@ -80,6 +82,7 @@ func NewMethodPicker[T any](config MethodPickerConfig[T]) MethodPicker[T] { picker.SetShowTitle(config.ShowTitle) method.list = picker + method.delegateGenner = config.Delegate return method } diff --git a/internal/tui/components/MethodPicker/config.go b/internal/tui/components/MethodPicker/config.go index 689a6d5..5ed7ccc 100644 --- a/internal/tui/components/MethodPicker/config.go +++ b/internal/tui/components/MethodPicker/config.go @@ -11,7 +11,7 @@ type MethodPickerConfig[T any] struct { ItemMapper func([]T) []list.Item FilteringEnabled bool - Delegate list.ItemDelegate + Delegate func(bool) list.ItemDelegate KeyMap list.KeyMap AdditionalKeymaps *keybinds.ListKeyMap diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index 9a63d9b..d91692a 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -23,6 +23,7 @@ type OptionsProvider[T, U any] struct { list list.Model input OptionsInput onSelectAction tea.Msg + delegateGenner func(bool) list.ItemDelegate keys *keybinds.ListKeyMap width int height int @@ -111,10 +112,11 @@ func (o OptionsProvider[T, U]) View() string { } func (o *OptionsProvider[T, U]) OnFocus() { + o.list.SetDelegate(o.delegateGenner(true)) } -func (o OptionsProvider[T, U]) OnBlur() { - +func (o *OptionsProvider[T, U]) OnBlur() { + o.list.SetDelegate(o.delegateGenner(false)) } func (o OptionsProvider[T, U]) GetSelected() Option { @@ -181,7 +183,7 @@ func initList[T, U any](config *ListConfig[T, U]) list.Model { items := config.ItemMapper(rawItems) - list := list.New(items, config.Delegate, 30, 30) + list := list.New(items, config.Delegate(true), 30, 30) // list configuration list.SetFilteringEnabled(config.FilteringEnabled) @@ -216,12 +218,13 @@ func NewOptionsProvider[T, U any](config *ListConfig[T, U]) OptionsProvider[T, U } return OptionsProvider[T, U]{ - list: initList(config), - focused: listComponent, - input: NewOptionsInput(&inputConfig), - getItems: config.GetItemsFunc, - itemMapper: config.ItemMapper, - keys: config.AdditionalKeymaps, - source: config.Source, + list: initList(config), + focused: listComponent, + input: NewOptionsInput(&inputConfig), + getItems: config.GetItemsFunc, + itemMapper: config.ItemMapper, + keys: config.AdditionalKeymaps, + source: config.Source, + delegateGenner: config.Delegate, } } diff --git a/internal/tui/components/OptionsProvider/config.go b/internal/tui/components/OptionsProvider/config.go index 9a0a096..0a05e18 100644 --- a/internal/tui/components/OptionsProvider/config.go +++ b/internal/tui/components/OptionsProvider/config.go @@ -21,7 +21,7 @@ type ListConfig[T, U any] struct { FilteringEnabled bool - Delegate list.ItemDelegate + Delegate func(bool) list.ItemDelegate KeyMap list.KeyMap AdditionalKeymaps *keybinds.ListKeyMap diff --git a/internal/tui/messages/messages.go b/internal/tui/messages/messages.go index a3192b6..21ad3b9 100644 --- a/internal/tui/messages/messages.go +++ b/internal/tui/messages/messages.go @@ -21,6 +21,7 @@ type DeactivateView struct{} type NavigateToView struct { ViewName string + Target string Data interface{} } diff --git a/internal/tui/styles/collections-styles.go b/internal/tui/styles/collections-styles.go index f044adb..b4d4393 100644 --- a/internal/tui/styles/collections-styles.go +++ b/internal/tui/styles/collections-styles.go @@ -3,7 +3,8 @@ package styles import "github.com/charmbracelet/lipgloss" var ( - SelectedListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) - UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) - InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) + SelectedInactiveListStyle = lipgloss.NewStyle().PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true) + SelectedActiveListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) + UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) + InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) ) diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index c528a1c..2c0b491 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -10,3 +10,7 @@ func RequestLayout(height, width int) func(...string) string { BorderLeft(true). Render } + +var ( + ActiveRequestItem = lipgloss.NewStyle() +) diff --git a/internal/tui/views/collections-view-helper.go b/internal/tui/views/collections-view-helper.go index 0dbbefc..75bd68d 100644 --- a/internal/tui/views/collections-view-helper.go +++ b/internal/tui/views/collections-view-helper.go @@ -7,23 +7,27 @@ import ( "github.com/yashranjan1/relay/internal/tui/styles" ) -func createCollectionDelegate() list.ItemDelegate { +func createCollectionDelegate(active bool) list.ItemDelegate { d := list.NewDefaultDelegate() + if active { + d.Styles.SelectedTitle = styles.SelectedActiveListStyle + } else { + d.Styles.SelectedTitle = styles.SelectedInactiveListStyle - d.Styles.SelectedTitle = styles.SelectedListStyle - d.Styles.SelectedDesc = styles.SelectedListStyle + } + d.Styles.SelectedDesc = styles.UnselectedListStyle return d } -func defaultListConfig[T, U any](binds *keybinds.ListKeyMap, delegateCreator func() list.ItemDelegate) *optionsProvider.ListConfig[T, U] { +func defaultListConfig[T, U any](binds *keybinds.ListKeyMap, delegateCreator func(bool) list.ItemDelegate) *optionsProvider.ListConfig[T, U] { config := optionsProvider.ListConfig[T, U]{ ShowPagination: true, ShowStatusBar: false, ShowHelp: false, ShowTitle: false, FilteringEnabled: true, - Delegate: delegateCreator(), + Delegate: delegateCreator, KeyMap: list.KeyMap{ CursorUp: binds.CursorUp, CursorDown: binds.CursorDown, diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go index 1ca21ba..7a4e659 100644 --- a/internal/tui/views/endpoints-view-helper.go +++ b/internal/tui/views/endpoints-view-helper.go @@ -12,10 +12,14 @@ import ( "github.com/maniac-en/req/internal/tui/styles" ) -func createEndpointsDelegate() list.ItemDelegate { +func createEndpointsDelegate(active bool) list.ItemDelegate { d := epItemDelegate{} - d.SelectedTitleStyle = styles.SelectedListStyle + if active { + d.SelectedTitleStyle = styles.SelectedActiveListStyle + } else { + d.SelectedTitleStyle = styles.SelectedInactiveListStyle + } d.UnselectedTitleStyle = styles.UnselectedListStyle return d diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 7f32fa9..2ad5536 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -71,7 +71,33 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.manager.Delete(context.Background(), msg.ItemID) e.list.RefreshItems() case messages.ChooseItem[optionsProvider.Option]: + e.list.OnBlur() + e.requestView.OnFocus() e.focused = requestView + case messages.NavigateToView: + if msg.Target != Endpoints { + break + } + e.requestView.OnBlur() + e.focused = listView + e.list.OnFocus() + + case tea.KeyMsg: + switch { + case key.Matches(msg, keybinds.Keys.Back): + navMsg := messages.NavigateToView{ + ViewName: Collections, + Target: MainModel, + } + switch e.focused { + case listView: + return e, func() tea.Msg { + return navMsg + } + case requestView: + e.requestView, cmd = e.requestView.Update(msg) + } + } } switch e.focused { case listView: diff --git a/internal/tui/views/request-view-helper.go b/internal/tui/views/request-view-helper.go new file mode 100644 index 0000000..38a30db --- /dev/null +++ b/internal/tui/views/request-view-helper.go @@ -0,0 +1,53 @@ +package views + +import ( + "fmt" + "io" + "strings" + + "github.com/charmbracelet/bubbles/list" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" + "github.com/maniac-en/req/internal/tui/styles" +) + +func createRequestDelegate(active bool) list.ItemDelegate { + d := requestDelegate{} + + if active { + d.SelectedTitleStyle = styles.SelectedActiveListStyle + } else { + d.SelectedTitleStyle = styles.SelectedInactiveListStyle + } + d.UnselectedTitleStyle = styles.UnselectedListStyle + + return d +} + +type requestDelegate struct { + SelectedTitleStyle lipgloss.Style + UnselectedTitleStyle lipgloss.Style +} + +func (r requestDelegate) Height() int { return 1 } +func (r requestDelegate) Spacing() int { return 1 } +func (r requestDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } +func (r requestDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { + item, ok := listItem.(methodpicker.MethodOption) + + if !ok { + return + } + + str := fmt.Sprintf("< %s >", item.Name) + + fn := r.UnselectedTitleStyle.Render + if index == m.Index() { + fn = func(s ...string) string { + return r.SelectedTitleStyle.Render(strings.Join(s, " ")) + } + } + + fmt.Fprint(w, fn(str)) +} diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 68c84b4..f40ea31 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -8,8 +8,10 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/maniac-en/req/internal/backend/database" + "github.com/maniac-en/req/internal/log" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" "github.com/maniac-en/req/internal/tui/keybinds" + "github.com/maniac-en/req/internal/tui/messages" "github.com/maniac-en/req/internal/tui/styles" ) @@ -23,29 +25,41 @@ type RequestView struct { endpoint database.Endpoint } -func (r RequestView) Init() tea.Cmd { +func (r *RequestView) Init() tea.Cmd { return nil } -func (r RequestView) Name() string { +func (r *RequestView) Name() string { return "Request" } -func (r RequestView) Help() []key.Binding { +func (r *RequestView) Help() []key.Binding { return []key.Binding{} } -func (r RequestView) GetFooterSegment() string { +func (r *RequestView) GetFooterSegment() string { return "" } -func (r RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { +func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { var cmd tea.Cmd var cmds []tea.Cmd switch msg := msg.(type) { case tea.WindowSizeMsg: r.height = msg.Height r.width = msg.Width + case tea.KeyMsg: + log.Info("received a key") + switch { + case key.Matches(msg, keybinds.Keys.Back): + log.Info("changing to ep") + return r, func() tea.Msg { + return messages.NavigateToView{ + ViewName: Endpoints, + Target: Endpoints, + } + } + } } r.methodPicker, cmd = r.methodPicker.Update(msg) @@ -54,11 +68,11 @@ func (r RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return r, tea.Batch(cmds...) } -func (r RequestView) View() string { +func (r *RequestView) View() string { return styles.RequestLayout(10, 100)(r.methodPicker.View()) } -func (r RequestView) SetState(items ...any) error { +func (r *RequestView) SetState(items ...any) error { if len(items) != 1 { return errors.New("Incorrect amount of fields supplied") } @@ -66,16 +80,16 @@ func (r RequestView) SetState(items ...any) error { return nil } -func (r RequestView) Order() int { +func (r *RequestView) Order() int { return r.order } -func (r RequestView) OnFocus() { - +func (r *RequestView) OnFocus() { + r.methodPicker.OnFocus() } -func (r RequestView) OnBlur() { - +func (r *RequestView) OnBlur() { + r.methodPicker.OnBlur() } func methodItemMapper(items []string) []list.Item { @@ -104,7 +118,7 @@ func NewRequestView() *RequestView { FilteringEnabled: false, ShowPagination: false, ShowStatusBar: false, - Delegate: list.DefaultDelegate{}, + Delegate: createRequestDelegate, ShowHelp: false, ShowTitle: false, Width: 30, diff --git a/internal/tui/views/view-reference.go b/internal/tui/views/view-reference.go new file mode 100644 index 0000000..0457768 --- /dev/null +++ b/internal/tui/views/view-reference.go @@ -0,0 +1,8 @@ +package views + +const ( + Collections = "collections" + Endpoints = "endpoints" + Request = "request" + MainModel = "main" +) diff --git a/main.go b/main.go index 315cf7d..eb7814f 100644 --- a/main.go +++ b/main.go @@ -70,6 +70,7 @@ func initPaths() error { } DBPATH = filepath.Join(APPDIR, "app.db") LOGPATH = filepath.Join(APPDIR, "req.log") + return nil } From 74b8ff8cd649b73e0a4a9c8db188cd7a98e3ee8f Mon Sep 17 00:00:00 2001 From: Yash Ranjan Date: Wed, 25 Feb 2026 23:17:54 +0100 Subject: [PATCH 26/46] url input created --- .../tui/components/ComponentTypes/types.go | 16 ++++ .../tui/components/MethodPicker/component.go | 22 ++--- .../tui/components/MethodPicker/config.go | 2 +- .../components/OptionsProvider/component.go | 4 + internal/tui/components/UrlInput/component.go | 61 +++++++++++++ internal/tui/components/UrlInput/config.go | 9 ++ internal/tui/keybinds/keys.go | 5 ++ internal/tui/styles/request-styles.go | 22 ++++- internal/tui/views/endpoints-view.go | 11 ++- internal/tui/views/request-view-helper.go | 42 ++++++++- internal/tui/views/request-view.go | 90 +++++++++++-------- 11 files changed, 230 insertions(+), 54 deletions(-) create mode 100644 internal/tui/components/ComponentTypes/types.go create mode 100644 internal/tui/components/UrlInput/component.go create mode 100644 internal/tui/components/UrlInput/config.go diff --git a/internal/tui/components/ComponentTypes/types.go b/internal/tui/components/ComponentTypes/types.go new file mode 100644 index 0000000..3e5ffcd --- /dev/null +++ b/internal/tui/components/ComponentTypes/types.go @@ -0,0 +1,16 @@ +package componenttypes + +import ( + "github.com/charmbracelet/bubbles/key" + tea "github.com/charmbracelet/bubbletea" +) + +type ComponentInterface interface { + Init() tea.Cmd + Update(tea.Msg) (ComponentInterface, tea.Cmd) + View() string + SetSize(int, int) + Help() []key.Binding + OnFocus() + OnBlur() +} diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go index 46718b7..f954b4b 100644 --- a/internal/tui/components/MethodPicker/component.go +++ b/internal/tui/components/MethodPicker/component.go @@ -4,6 +4,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" "github.com/maniac-en/req/internal/tui/keybinds" ) @@ -27,11 +28,14 @@ func (m MethodOption) Description() string { return m.Name } func (m MethodOption) Value() string { return m.Type } func (m MethodOption) FilterValue() string { return m.Name } -func (m MethodPicker[T]) Init() tea.Cmd { +func (m *MethodPicker[T]) Init() tea.Cmd { return nil } -func (m MethodPicker[T]) Update(msg tea.Msg) (MethodPicker[T], tea.Cmd) { +func (m *MethodPicker[T]) SetSize(width, height int) { +} + +func (m *MethodPicker[T]) Update(msg tea.Msg) (componenttypes.ComponentInterface, tea.Cmd) { var cmd tea.Cmd m.list, cmd = m.list.Update(msg) @@ -39,7 +43,7 @@ func (m MethodPicker[T]) Update(msg tea.Msg) (MethodPicker[T], tea.Cmd) { return m, cmd } -func (m MethodPicker[T]) View() string { +func (m *MethodPicker[T]) View() string { return m.list.View() } @@ -51,22 +55,19 @@ func (m *MethodPicker[T]) OnBlur() { m.list.SetDelegate(m.delegateGenner(false)) } -func (m MethodPicker[T]) GetSelected() MethodOption { +func (m *MethodPicker[T]) GetSelected() MethodOption { return m.list.SelectedItem().(MethodOption) } func (m *MethodPicker[T]) Help() []key.Binding { return []key.Binding{ - m.keys.NextPage, - m.keys.PrevPage, - m.keys.CursorUp, m.keys.CursorDown, - m.keys.Accept, + m.keys.CursorUp, } } -func NewMethodPicker[T any](config MethodPickerConfig[T]) MethodPicker[T] { - method := MethodPicker[T]{} +func NewMethodPicker[T any](config MethodPickerConfig[T]) *MethodPicker[T] { + method := &MethodPicker[T]{} picker := list.New( config.ItemMapper(config.Items), @@ -82,6 +83,7 @@ func NewMethodPicker[T any](config MethodPickerConfig[T]) MethodPicker[T] { picker.SetShowTitle(config.ShowTitle) method.list = picker + method.keys = config.KeyMap method.delegateGenner = config.Delegate return method diff --git a/internal/tui/components/MethodPicker/config.go b/internal/tui/components/MethodPicker/config.go index 5ed7ccc..2408bc6 100644 --- a/internal/tui/components/MethodPicker/config.go +++ b/internal/tui/components/MethodPicker/config.go @@ -12,7 +12,7 @@ type MethodPickerConfig[T any] struct { FilteringEnabled bool Delegate func(bool) list.ItemDelegate - KeyMap list.KeyMap + KeyMap *keybinds.ListKeyMap AdditionalKeymaps *keybinds.ListKeyMap OnSelectAction tea.Msg diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index d91692a..8e7f458 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -143,6 +143,10 @@ func (o *OptionsProvider[T, U]) RefreshItems() { o.list.SetItems(o.itemMapper(newItems)) } +func (o *OptionsProvider[T, U]) GetSize() (int, int) { + return o.width, o.height +} + func (o *OptionsProvider[T, U]) Help() []key.Binding { var binds []key.Binding switch o.focused { diff --git a/internal/tui/components/UrlInput/component.go b/internal/tui/components/UrlInput/component.go new file mode 100644 index 0000000..23d5ad2 --- /dev/null +++ b/internal/tui/components/UrlInput/component.go @@ -0,0 +1,61 @@ +package urlinput + +import ( + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" +) + +type UrlInput struct { + text textinput.Model + styleGenner func(bool) lipgloss.Style + width int + height int + active bool +} + +func (u *UrlInput) Init() tea.Cmd { + return nil +} + +func (u *UrlInput) SetSize(width, height int) { + u.text.PromptStyle.Height(height) + u.text.PromptStyle.Width(width) +} + +func (u *UrlInput) Help() []key.Binding { + return []key.Binding{} +} + +func (u *UrlInput) Update(msg tea.Msg) (componenttypes.ComponentInterface, tea.Cmd) { + var cmd tea.Cmd + + u.text, cmd = u.text.Update(msg) + + return u, cmd +} + +func (u *UrlInput) View() string { + return u.styleGenner(u.active).Render(u.text.View()) +} + +func (u *UrlInput) OnFocus() { + u.text.Focus() + u.active = true +} + +func (u *UrlInput) OnBlur() { + u.active = false +} + +func NewUrlInput(config UrlInputConfig) *UrlInput { + return &UrlInput{ + text: textinput.New(), + width: config.Width, + height: config.Height, + styleGenner: config.StyleGenner, + active: false, + } +} diff --git a/internal/tui/components/UrlInput/config.go b/internal/tui/components/UrlInput/config.go new file mode 100644 index 0000000..7e5b16a --- /dev/null +++ b/internal/tui/components/UrlInput/config.go @@ -0,0 +1,9 @@ +package urlinput + +import "github.com/charmbracelet/lipgloss" + +type UrlInputConfig struct { + StyleGenner func(bool) lipgloss.Style + Width int + Height int +} diff --git a/internal/tui/keybinds/keys.go b/internal/tui/keybinds/keys.go index f731a98..99f5b08 100644 --- a/internal/tui/keybinds/keys.go +++ b/internal/tui/keybinds/keys.go @@ -14,6 +14,7 @@ type Keymaps struct { Up key.Binding Down key.Binding NextPage key.Binding + Next key.Binding PrevPage key.Binding Filter key.Binding ClearFilter key.Binding @@ -31,6 +32,10 @@ var Keys = Keymaps{ key.WithKeys("up", "k"), key.WithHelp("↑/k", "back"), ), + Next: key.NewBinding( + key.WithKeys("tab"), + key.WithHelp("tab", "next field"), + ), Down: key.NewBinding( key.WithKeys("down", "j"), key.WithHelp("↓/j", "down"), diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index 2c0b491..9819472 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -6,11 +6,27 @@ func RequestLayout(height, width int) func(...string) string { return lipgloss.NewStyle(). Height(height). Width(width). - Padding(1, 0, 0, 1). - BorderLeft(true). + Margin(0, 0, 0, 5). + Padding(1, 0, 0, 10). + Border(lipgloss.NormalBorder(), false, false, false, true). Render } +func UrlInputStyle(active bool) lipgloss.Style { + var color lipgloss.Color + + if active { + color = accent + } + + return lipgloss.NewStyle(). + Foreground(color). + Margin(0, 0, 0, 5). + Border(lipgloss.NormalBorder(), false, false, true, false). + BorderForeground(color) +} + var ( - ActiveRequestItem = lipgloss.NewStyle() + ActiveRequestItem = lipgloss.NewStyle().Foreground(accent) + InactiveRequestItem = lipgloss.NewStyle() ) diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 2ad5536..7083ab4 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -43,7 +43,11 @@ func (e *EndpointsView) Name() string { } func (e *EndpointsView) Help() []key.Binding { - return e.list.Help() + if e.focused == listView { + return e.list.Help() + } else { + return e.requestView.Help() + } } func (e *EndpointsView) GetFooterSegment() string { @@ -58,6 +62,11 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.height = msg.Height e.width = msg.Width e.list, cmd = e.list.Update(msg) + w, _ := e.list.GetSize() + e.requestView, cmd = e.requestView.Update(tea.WindowSizeMsg{ + Height: msg.Height, + Width: msg.Width - w, + }) cmds = append(cmds, cmd) case messages.ItemAdded: e.manager.CreateEndpoint(context.Background(), endpoints.EndpointData{ diff --git a/internal/tui/views/request-view-helper.go b/internal/tui/views/request-view-helper.go index 38a30db..b935c15 100644 --- a/internal/tui/views/request-view-helper.go +++ b/internal/tui/views/request-view-helper.go @@ -9,6 +9,8 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" + urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" + "github.com/maniac-en/req/internal/tui/keybinds" "github.com/maniac-en/req/internal/tui/styles" ) @@ -16,11 +18,11 @@ func createRequestDelegate(active bool) list.ItemDelegate { d := requestDelegate{} if active { - d.SelectedTitleStyle = styles.SelectedActiveListStyle + d.SelectedTitleStyle = styles.ActiveRequestItem } else { - d.SelectedTitleStyle = styles.SelectedInactiveListStyle + d.SelectedTitleStyle = styles.InactiveRequestItem } - d.UnselectedTitleStyle = styles.UnselectedListStyle + d.UnselectedTitleStyle = styles.InactiveRequestItem return d } @@ -51,3 +53,37 @@ func (r requestDelegate) Render(w io.Writer, m list.Model, index int, listItem l fmt.Fprint(w, fn(str)) } + +func createMethodPickerConfig() methodpicker.MethodPickerConfig[string] { + methods := []string{ + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + } + + keys := keybinds.NewListKeyMap() + + return methodpicker.MethodPickerConfig[string]{ + Items: methods, + ItemMapper: methodItemMapper, + FilteringEnabled: false, + ShowPagination: false, + ShowStatusBar: false, + KeyMap: keys, + Delegate: createRequestDelegate, + ShowHelp: false, + ShowTitle: false, + Width: 30, + Height: 1, + } +} + +func createURLInputConfig() urlinput.UrlInputConfig { + return urlinput.UrlInputConfig{ + Width: 50, + Height: 1, + StyleGenner: styles.UrlInputStyle, + } +} diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index f40ea31..5416558 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -7,22 +7,38 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/database" - "github.com/maniac-en/req/internal/log" + componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" + urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" "github.com/maniac-en/req/internal/tui/keybinds" "github.com/maniac-en/req/internal/tui/messages" "github.com/maniac-en/req/internal/tui/styles" ) +type reqFocused string + +const ( + methodPicker = "method" + urlInput = "input" +) + +var componentList = []reqFocused{ + methodPicker, + urlInput, +} + type RequestView struct { - width int - height int - help help.Model - keys *keybinds.ListKeyMap - methodPicker methodpicker.MethodPicker[string] - order int - endpoint database.Endpoint + width int + focused reqFocused + components map[reqFocused]componenttypes.ComponentInterface + index int + height int + help help.Model + keys *keybinds.ListKeyMap + order int + endpoint database.Endpoint } func (r *RequestView) Init() tea.Cmd { @@ -34,7 +50,8 @@ func (r *RequestView) Name() string { } func (r *RequestView) Help() []key.Binding { - return []key.Binding{} + binds := r.components[r.focused].Help() + return append(binds, keybinds.Keys.Next) } func (r *RequestView) GetFooterSegment() string { @@ -48,35 +65,48 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case tea.WindowSizeMsg: r.height = msg.Height r.width = msg.Width + r.components[urlInput].SetSize(r.width-20, r.height) case tea.KeyMsg: - log.Info("received a key") switch { case key.Matches(msg, keybinds.Keys.Back): - log.Info("changing to ep") return r, func() tea.Msg { return messages.NavigateToView{ ViewName: Endpoints, Target: Endpoints, } } + case key.Matches(msg, keybinds.Keys.Next): + r.index = (r.index + 1) % len(componentList) + r.components[r.focused].OnBlur() + r.focused = componentList[r.index] + r.components[r.focused].OnFocus() + return r, nil } } - r.methodPicker, cmd = r.methodPicker.Update(msg) + r.components[r.focused], cmd = r.components[r.focused].Update(msg) cmds = append(cmds, cmd) return r, tea.Batch(cmds...) } func (r *RequestView) View() string { - return styles.RequestLayout(10, 100)(r.methodPicker.View()) + views := []string{} + for _, val := range componentList { + views = append(views, r.components[val].View()) + } + view := lipgloss.JoinHorizontal( + lipgloss.Left, + views..., + ) + return styles.RequestLayout(r.height, r.width)(view) } func (r *RequestView) SetState(items ...any) error { if len(items) != 1 { return errors.New("Incorrect amount of fields supplied") } - r.SetState(items[0]) + // r.SetState(items[0]) return nil } @@ -85,11 +115,11 @@ func (r *RequestView) Order() int { } func (r *RequestView) OnFocus() { - r.methodPicker.OnFocus() + r.components[r.focused].OnFocus() } func (r *RequestView) OnBlur() { - r.methodPicker.OnBlur() + r.components[r.focused].OnBlur() } func methodItemMapper(items []string) []list.Item { @@ -104,27 +134,15 @@ func methodItemMapper(items []string) []list.Item { } func NewRequestView() *RequestView { - methods := []string{ - "GET", - "POST", - "PUT", - "PATCH", - "DELETE", - } + mpConfig := createMethodPickerConfig() + + uiConfig := createURLInputConfig() - config := methodpicker.MethodPickerConfig[string]{ - Items: methods, - ItemMapper: methodItemMapper, - FilteringEnabled: false, - ShowPagination: false, - ShowStatusBar: false, - Delegate: createRequestDelegate, - ShowHelp: false, - ShowTitle: false, - Width: 30, - Height: 1, - } return &RequestView{ - methodPicker: methodpicker.NewMethodPicker[string](config), + components: map[reqFocused]componenttypes.ComponentInterface{ + methodPicker: methodpicker.NewMethodPicker(mpConfig), + urlInput: urlinput.NewUrlInput(uiConfig), + }, + focused: methodPicker, } } From 306bf47bb822a33b3a11644c499c0c6f5cb23d89 Mon Sep 17 00:00:00 2001 From: Yash Ranjan Date: Fri, 27 Feb 2026 15:20:24 +0100 Subject: [PATCH 27/46] Additions - URL box width fixed - Request view now updates based on ep selection - Changes to request view now save - Changes to request view cause items in the ep list to update - Added a placeholder for the url box --- .../tui/components/ComponentTypes/types.go | 10 ++- .../tui/components/MethodPicker/component.go | 33 +++++++++- .../components/OptionsProvider/component.go | 14 ++++- .../tui/components/OptionsProvider/config.go | 1 + internal/tui/components/UrlInput/component.go | 22 +++++-- internal/tui/keybinds/keys.go | 13 ++-- internal/tui/views/endpoints-view.go | 17 ++++- internal/tui/views/request-view.go | 63 ++++++++++++++----- 8 files changed, 142 insertions(+), 31 deletions(-) diff --git a/internal/tui/components/ComponentTypes/types.go b/internal/tui/components/ComponentTypes/types.go index 3e5ffcd..2c5c8c9 100644 --- a/internal/tui/components/ComponentTypes/types.go +++ b/internal/tui/components/ComponentTypes/types.go @@ -3,13 +3,17 @@ package componenttypes import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/backend/endpoints" ) -type ComponentInterface interface { +type ReqViewComponent interface { Init() tea.Cmd - Update(tea.Msg) (ComponentInterface, tea.Cmd) + Update(tea.Msg) (ReqViewComponent, tea.Cmd) View() string - SetSize(int, int) + SetWidth(int) + UpdateState(endpoints.EndpointEntity) endpoints.EndpointEntity + SetState(endpoints.EndpointEntity) + GetWidth() int Help() []key.Binding OnFocus() OnBlur() diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go index f954b4b..d73dc15 100644 --- a/internal/tui/components/MethodPicker/component.go +++ b/internal/tui/components/MethodPicker/component.go @@ -4,6 +4,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/backend/endpoints" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" "github.com/maniac-en/req/internal/tui/keybinds" ) @@ -13,6 +14,7 @@ type MethodPicker[T any] struct { onSelectAction tea.Msg delegateGenner func(bool) list.ItemDelegate keys *keybinds.ListKeyMap + state endpoints.EndpointEntity width int height int itemMapper func([]T) []list.Item @@ -32,10 +34,37 @@ func (m *MethodPicker[T]) Init() tea.Cmd { return nil } -func (m *MethodPicker[T]) SetSize(width, height int) { +func (m *MethodPicker[T]) SetWidth(width int) { + m.list.SetWidth(width) } -func (m *MethodPicker[T]) Update(msg tea.Msg) (componenttypes.ComponentInterface, tea.Cmd) { +func (m *MethodPicker[T]) GetWidth() int { + return m.list.Width() +} + +func (m *MethodPicker[T]) SetState(ep endpoints.EndpointEntity) { + if index := m.findIndex(ep.Method); index >= 0 { + m.list.Select(index) + m.state = ep + } +} + +func (m *MethodPicker[T]) UpdateState(ep endpoints.EndpointEntity) endpoints.EndpointEntity { + ep.Method = m.list.SelectedItem().(MethodOption).Name + return ep +} + +func (m *MethodPicker[T]) findIndex(method string) int { + items := m.list.Items() + for index, item := range items { + if option, ok := item.(MethodOption); ok && option.Name == method { + return index + } + } + return -1 +} + +func (m *MethodPicker[T]) Update(msg tea.Msg) (componenttypes.ReqViewComponent, tea.Cmd) { var cmd tea.Cmd m.list, cmd = m.list.Update(msg) diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index 8e7f458..a2c4134 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -23,6 +23,7 @@ type OptionsProvider[T, U any] struct { list list.Model input OptionsInput onSelectAction tea.Msg + onChangeAction func(int64) delegateGenner func(bool) list.ItemDelegate keys *keybinds.ListKeyMap width int @@ -44,7 +45,7 @@ func (o Option) Description() string { return o.Subtext } func (o Option) Value() int64 { return o.ID } func (o Option) FilterValue() string { return o.Name } -func (o OptionsProvider[T, U]) Init() tea.Cmd { +func (o *OptionsProvider[T, U]) Init() tea.Cmd { return nil } @@ -96,7 +97,12 @@ func (o OptionsProvider[T, U]) Update(msg tea.Msg) (OptionsProvider[T, U], tea.C } switch o.focused { case listComponent: + index := o.list.Index() o.list, cmd = o.list.Update(msg) + newIndex := o.list.Index() + if newIndex != index && o.onChangeAction != nil { + o.onChangeAction(o.GetSelected().ID) + } case textComponent: o.input, cmd = o.input.Update(msg) } @@ -207,6 +213,10 @@ func (o *OptionsProvider[T, U]) SetGetItemsFunc(getItems func(context.Context) ( log.Error("error fetching items") } o.list.SetItems(o.itemMapper(items)) + if o.onChangeAction != nil { + o.onChangeAction(o.itemMapper(items)[0].(Option).ID) + } + } func NewOptionsProvider[T, U any](config *ListConfig[T, U]) OptionsProvider[T, U] { @@ -230,5 +240,7 @@ func NewOptionsProvider[T, U any](config *ListConfig[T, U]) OptionsProvider[T, U keys: config.AdditionalKeymaps, source: config.Source, delegateGenner: config.Delegate, + onChangeAction: config.OnChangeAction, } + } diff --git a/internal/tui/components/OptionsProvider/config.go b/internal/tui/components/OptionsProvider/config.go index 0a05e18..eabf0e7 100644 --- a/internal/tui/components/OptionsProvider/config.go +++ b/internal/tui/components/OptionsProvider/config.go @@ -12,6 +12,7 @@ import ( type ListConfig[T, U any] struct { OnSelectAction tea.Msg + OnChangeAction func(int64) ShowPagination bool ShowStatusBar bool diff --git a/internal/tui/components/UrlInput/component.go b/internal/tui/components/UrlInput/component.go index 23d5ad2..f80e40d 100644 --- a/internal/tui/components/UrlInput/component.go +++ b/internal/tui/components/UrlInput/component.go @@ -5,6 +5,7 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/maniac-en/req/internal/backend/endpoints" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" ) @@ -20,16 +21,28 @@ func (u *UrlInput) Init() tea.Cmd { return nil } -func (u *UrlInput) SetSize(width, height int) { - u.text.PromptStyle.Height(height) - u.text.PromptStyle.Width(width) +func (u *UrlInput) SetWidth(width int) { + u.text.Width = width +} + +func (u *UrlInput) GetWidth() int { + return u.text.Width } func (u *UrlInput) Help() []key.Binding { return []key.Binding{} } -func (u *UrlInput) Update(msg tea.Msg) (componenttypes.ComponentInterface, tea.Cmd) { +func (u *UrlInput) SetState(ep endpoints.EndpointEntity) { + u.text.SetValue(ep.Url) +} + +func (u *UrlInput) UpdateState(ep endpoints.EndpointEntity) endpoints.EndpointEntity { + ep.Url = u.text.Value() + return ep +} + +func (u *UrlInput) Update(msg tea.Msg) (componenttypes.ReqViewComponent, tea.Cmd) { var cmd tea.Cmd u.text, cmd = u.text.Update(msg) @@ -47,6 +60,7 @@ func (u *UrlInput) OnFocus() { } func (u *UrlInput) OnBlur() { + u.text.Blur() u.active = false } diff --git a/internal/tui/keybinds/keys.go b/internal/tui/keybinds/keys.go index 99f5b08..661eb61 100644 --- a/internal/tui/keybinds/keys.go +++ b/internal/tui/keybinds/keys.go @@ -15,6 +15,7 @@ type Keymaps struct { Down key.Binding NextPage key.Binding Next key.Binding + Prev key.Binding PrevPage key.Binding Filter key.Binding ClearFilter key.Binding @@ -25,16 +26,20 @@ type Keymaps struct { var Keys = Keymaps{ Back: key.NewBinding( - key.WithKeys("esc", "q"), - key.WithHelp("esc/q", "back"), + key.WithKeys("ctrl+b"), + key.WithHelp("esc", "back"), ), Up: key.NewBinding( key.WithKeys("up", "k"), key.WithHelp("↑/k", "back"), ), + Prev: key.NewBinding( + key.WithKeys("ctrl+h"), + key.WithHelp("ctrl+h", "prev field"), + ), Next: key.NewBinding( - key.WithKeys("tab"), - key.WithHelp("tab", "next field"), + key.WithKeys("ctrl+l"), + key.WithHelp("ctrl+l", "next field"), ), Down: key.NewBinding( key.WithKeys("down", "j"), diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 7083ab4..f0bd8db 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -62,7 +62,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.height = msg.Height e.width = msg.Width e.list, cmd = e.list.Update(msg) - w, _ := e.list.GetSize() + _, w := e.list.GetSize() e.requestView, cmd = e.requestView.Update(tea.WindowSizeMsg{ Height: msg.Height, Width: msg.Width - w, @@ -74,6 +74,8 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { Name: msg.Item, Method: "GET", }) + case messages.RefreshItemsList: + e.list.RefreshItems() case messages.ItemEdited: e.manager.UpdateEndpointName(context.Background(), msg.ItemID, msg.Item) case messages.DeleteItem: @@ -129,6 +131,7 @@ func (e *EndpointsView) OnFocus() { } func (e *EndpointsView) SetState(items ...any) error { + // TODO: Something over here should also set the state for the request view if len(items) == 1 { if collection, ok := items[0].(optionsProvider.Option); ok { e.collection = collection @@ -177,6 +180,17 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin keybinds := keybinds.NewListKeyMap() config := defaultListConfig[endpoints.EndpointEntity, database.Endpoint](keybinds, createEndpointsDelegate) + epUpdateFunc := epManager.UpdateEndpoint + view.requestView = NewRequestView(epUpdateFunc) + + config.OnChangeAction = func(id int64) { + epDetails, err := epManager.Read(context.Background(), id) + if err != nil { + // TODO: what do we do here? prob throw a crash ig? + } + view.requestView.SetState(epDetails) + } + epListFunc := func(ctx context.Context) ([]endpoints.EndpointEntity, error) { return epManager.ListByCollection(ctx, view.collection.ID) } @@ -187,7 +201,6 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin config.Source = "endpoints" view.list = optionsProvider.NewOptionsProvider(config) - view.requestView = NewRequestView() view.focused = listView return view diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 5416558..f6f740c 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -1,6 +1,7 @@ package views import ( + "context" "errors" "github.com/charmbracelet/bubbles/help" @@ -8,7 +9,7 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/maniac-en/req/internal/backend/database" + "github.com/maniac-en/req/internal/backend/endpoints" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" @@ -32,13 +33,14 @@ var componentList = []reqFocused{ type RequestView struct { width int focused reqFocused - components map[reqFocused]componenttypes.ComponentInterface + components map[reqFocused]componenttypes.ReqViewComponent index int height int help help.Model keys *keybinds.ListKeyMap order int - endpoint database.Endpoint + update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) + endpoint endpoints.EndpointEntity } func (r *RequestView) Init() tea.Cmd { @@ -65,7 +67,8 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case tea.WindowSizeMsg: r.height = msg.Height r.width = msg.Width - r.components[urlInput].SetSize(r.width-20, r.height) + w := r.components[urlInput].GetWidth() + r.components[urlInput].SetWidth(r.width - (w + 30)) case tea.KeyMsg: switch { case key.Matches(msg, keybinds.Keys.Back): @@ -76,11 +79,15 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { } } case key.Matches(msg, keybinds.Keys.Next): - r.index = (r.index + 1) % len(componentList) - r.components[r.focused].OnBlur() - r.focused = componentList[r.index] - r.components[r.focused].OnFocus() - return r, nil + r.shift(true) + return r, func() tea.Msg { + return messages.RefreshItemsList{} + } + case key.Matches(msg, keybinds.Keys.Prev): + r.shift(false) + return r, func() tea.Msg { + return messages.RefreshItemsList{} + } } } @@ -90,6 +97,26 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return r, tea.Batch(cmds...) } +func (r *RequestView) shift(next bool) { + if next { + r.index = (r.index + 1) % len(componentList) + } else { + r.index = (r.index - 1) % len(componentList) + } + r.endpoint = r.components[r.focused].UpdateState(r.endpoint) + r.components[r.focused].OnBlur() + r.focused = componentList[r.index] + r.components[r.focused].OnFocus() + r.update( + context.Background(), + r.endpoint.GetID(), + endpoints.EndpointData{ + Name: r.endpoint.Name, + Method: r.endpoint.Method, + URL: r.endpoint.Url, + }) +} + func (r *RequestView) View() string { views := []string{} for _, val := range componentList { @@ -103,11 +130,16 @@ func (r *RequestView) View() string { } func (r *RequestView) SetState(items ...any) error { - if len(items) != 1 { - return errors.New("Incorrect amount of fields supplied") + if len(items) == 1 { + if ep, ok := items[0].(endpoints.EndpointEntity); ok { + r.endpoint = ep + for _, val := range componentList { + r.components[val].SetState(ep) + } + return nil + } } - // r.SetState(items[0]) - return nil + return errors.New("Invalid inputs, this function takes 1 input of type endpoints.Entity") } func (r *RequestView) Order() int { @@ -133,16 +165,17 @@ func methodItemMapper(items []string) []list.Item { return listItems } -func NewRequestView() *RequestView { +func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error)) *RequestView { mpConfig := createMethodPickerConfig() uiConfig := createURLInputConfig() return &RequestView{ - components: map[reqFocused]componenttypes.ComponentInterface{ + components: map[reqFocused]componenttypes.ReqViewComponent{ methodPicker: methodpicker.NewMethodPicker(mpConfig), urlInput: urlinput.NewUrlInput(uiConfig), }, focused: methodPicker, + update: update, } } From 000fee23fdb05e9562e2d9f5fa007b42739b9b09 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 27 Feb 2026 23:37:06 +0100 Subject: [PATCH 28/46] Additions: - Skeleton for a response view added - You can now send requests and see the response for it --- internal/tui/keybinds/keys.go | 14 ++- internal/tui/styles/request-styles.go | 4 + internal/tui/views/endpoints-view.go | 1 + internal/tui/views/request-view.go | 134 +++++++++++++++++++++----- 4 files changed, 125 insertions(+), 28 deletions(-) diff --git a/internal/tui/keybinds/keys.go b/internal/tui/keybinds/keys.go index 661eb61..dcc1193 100644 --- a/internal/tui/keybinds/keys.go +++ b/internal/tui/keybinds/keys.go @@ -14,8 +14,10 @@ type Keymaps struct { Up key.Binding Down key.Binding NextPage key.Binding + SendRequest key.Binding Next key.Binding Prev key.Binding + Save key.Binding PrevPage key.Binding Filter key.Binding ClearFilter key.Binding @@ -27,11 +29,19 @@ type Keymaps struct { var Keys = Keymaps{ Back: key.NewBinding( key.WithKeys("ctrl+b"), - key.WithHelp("esc", "back"), + key.WithHelp("ctrl+b", "back"), + ), + SendRequest: key.NewBinding( + key.WithKeys("ctrl+r"), + key.WithHelp("ctrl+r", "send request"), + ), + Save: key.NewBinding( + key.WithKeys("ctrl+s"), + key.WithHelp("ctrl+s", "save"), ), Up: key.NewBinding( key.WithKeys("up", "k"), - key.WithHelp("↑/k", "back"), + key.WithHelp("↑/k", "up"), ), Prev: key.NewBinding( key.WithKeys("ctrl+h"), diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index 9819472..af44b01 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -26,6 +26,10 @@ func UrlInputStyle(active bool) lipgloss.Style { BorderForeground(color) } +func ResponseStyle(height, width int) func(...string) string { + return lipgloss.NewStyle().Margin(0, 0, 0, 5).Border(lipgloss.NormalBorder(), false, false, false, true).Padding(1, 0, 0, 5).Height(height).Width(width).Render +} + var ( ActiveRequestItem = lipgloss.NewStyle().Foreground(accent) InactiveRequestItem = lipgloss.NewStyle() diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index f0bd8db..35ae9b3 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -107,6 +107,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { } case requestView: e.requestView, cmd = e.requestView.Update(msg) + return e, nil } } } diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index f6f740c..327f34e 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -3,13 +3,17 @@ package views import ( "context" "errors" + "fmt" "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/endpoints" + "github.com/maniac-en/req/internal/backend/http" + "github.com/maniac-en/req/internal/log" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" @@ -31,16 +35,21 @@ var componentList = []reqFocused{ } type RequestView struct { - width int - focused reqFocused - components map[reqFocused]componenttypes.ReqViewComponent - index int - height int - help help.Model - keys *keybinds.ListKeyMap - order int - update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) - endpoint endpoints.EndpointEntity + width int + focused reqFocused + components map[reqFocused]componenttypes.ReqViewComponent + index int + height int + loading bool + help help.Model + keys *keybinds.ListKeyMap + spinner spinner.Model + responsePage bool + client *http.HTTPManager + order int + res *http.Response + update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) + endpoint endpoints.EndpointEntity } func (r *RequestView) Init() tea.Cmd { @@ -53,7 +62,13 @@ func (r *RequestView) Name() string { func (r *RequestView) Help() []key.Binding { binds := r.components[r.focused].Help() - return append(binds, keybinds.Keys.Next) + reqViewBinds := []key.Binding{ + keybinds.Keys.Prev, + keybinds.Keys.Next, + keybinds.Keys.Save, + keybinds.Keys.SendRequest, + } + return append(binds, reqViewBinds...) } func (r *RequestView) GetFooterSegment() string { @@ -72,10 +87,15 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, keybinds.Keys.Back): - return r, func() tea.Msg { - return messages.NavigateToView{ - ViewName: Endpoints, - Target: Endpoints, + if r.responsePage { + r.responsePage = false + return r, nil + } else { + return r, func() tea.Msg { + return messages.NavigateToView{ + ViewName: Endpoints, + Target: Endpoints, + } } } case key.Matches(msg, keybinds.Keys.Next): @@ -88,12 +108,47 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return r, func() tea.Msg { return messages.RefreshItemsList{} } + case key.Matches(msg, keybinds.Keys.SendRequest): + request := &http.Request{ + Method: r.endpoint.Method, + URL: r.endpoint.Url, + } + r.responsePage = true + r.loading = true + res, err := r.client.ExecuteRequest(request) + r.loading = false + r.res = res + if err != nil { + log.Warn("error occurred while trying to send a request", err) + } + return r, r.spinner.Tick + case key.Matches(msg, keybinds.Keys.Save): + for _, val := range r.components { + r.endpoint = val.UpdateState(r.endpoint) + } + r.update( + context.Background(), + r.endpoint.GetID(), + endpoints.EndpointData{ + Name: r.endpoint.Name, + Method: r.endpoint.Method, + URL: r.endpoint.Url, + }) } } - r.components[r.focused], cmd = r.components[r.focused].Update(msg) + if !r.responsePage { + r.components[r.focused], cmd = r.components[r.focused].Update(msg) + } + cmds = append(cmds, cmd) + if r.loading { + log.Info("working") + r.spinner, cmd = r.spinner.Update(msg) + cmds = append(cmds, cmd) + } + return r, tea.Batch(cmds...) } @@ -118,15 +173,37 @@ func (r *RequestView) shift(next bool) { } func (r *RequestView) View() string { - views := []string{} - for _, val := range componentList { - views = append(views, r.components[val].View()) + if r.responsePage { + if r.res == nil { + return styles.RequestLayout(r.height, r.width)(lipgloss.Place(r.width, r.height-1, lipgloss.Center, lipgloss.Center, "No responses so far")) + } + if r.loading { + loadingString := fmt.Sprintf("%s Loading...", r.spinner.View()) + return styles.RequestLayout(r.height, r.width)(lipgloss.Place(r.width, r.height-1, lipgloss.Center, lipgloss.Center, loadingString)) + } + // TODO: use a viewport here PLEASE => https://github.com/charmbracelet/bubbles?tab=readme-ov-file#viewport + responseDetails := []string{ + fmt.Sprintf("Status Code: %d", r.res.StatusCode), + fmt.Sprintf("Body: %s", r.res.Body), + } + for key, val := range r.res.Headers { + for _, item := range val { + responseDetails = append(responseDetails, fmt.Sprintf("%s: %s", key, item)) + } + } + view := styles.ResponseStyle(r.height, r.width)(lipgloss.JoinVertical(lipgloss.Top, responseDetails...)) + return view + } else { + views := []string{} + for _, val := range componentList { + views = append(views, r.components[val].View()) + } + view := lipgloss.JoinHorizontal( + lipgloss.Left, + views..., + ) + return styles.RequestLayout(r.height, r.width)(view) } - view := lipgloss.JoinHorizontal( - lipgloss.Left, - views..., - ) - return styles.RequestLayout(r.height, r.width)(view) } func (r *RequestView) SetState(items ...any) error { @@ -169,13 +246,18 @@ func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) mpConfig := createMethodPickerConfig() uiConfig := createURLInputConfig() + s := spinner.New() + s.Spinner = spinner.Dot return &RequestView{ components: map[reqFocused]componenttypes.ReqViewComponent{ methodPicker: methodpicker.NewMethodPicker(mpConfig), urlInput: urlinput.NewUrlInput(uiConfig), }, - focused: methodPicker, - update: update, + focused: methodPicker, + update: update, + client: http.NewHTTPManager(), + responsePage: false, + spinner: s, } } From 85ada8dae91c3f82495d201b39c579510219fcc4 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Sun, 1 Mar 2026 19:43:41 +0100 Subject: [PATCH 29/46] minor bug fix --- internal/tui/views/endpoints-view.go | 16 ++++++---------- internal/tui/views/request-view.go | 11 +++++------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 35ae9b3..4b28c08 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -96,21 +96,17 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, keybinds.Keys.Back): - navMsg := messages.NavigateToView{ - ViewName: Collections, - Target: MainModel, - } - switch e.focused { - case listView: + if e.focused == listView { return e, func() tea.Msg { - return navMsg + return messages.NavigateToView{ + ViewName: Collections, + Target: MainModel, + } } - case requestView: - e.requestView, cmd = e.requestView.Update(msg) - return e, nil } } } + switch e.focused { case listView: e.list, cmd = e.list.Update(msg) diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 327f34e..89edd56 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -90,12 +90,11 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { if r.responsePage { r.responsePage = false return r, nil - } else { - return r, func() tea.Msg { - return messages.NavigateToView{ - ViewName: Endpoints, - Target: Endpoints, - } + } + return r, func() tea.Msg { + return messages.NavigateToView{ + ViewName: Endpoints, + Target: Endpoints, } } case key.Matches(msg, keybinds.Keys.Next): From 36b928737032be5222743db4025ddd6818c75bcc Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Sun, 1 Mar 2026 23:32:22 +0100 Subject: [PATCH 30/46] viewport component added, needs more polishing --- internal/tui/components/ViewPort/component.go | 82 +++++++++++++++++++ internal/tui/views/request-view.go | 28 +++---- 2 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 internal/tui/components/ViewPort/component.go diff --git a/internal/tui/components/ViewPort/component.go b/internal/tui/components/ViewPort/component.go new file mode 100644 index 0000000..0533bf1 --- /dev/null +++ b/internal/tui/components/ViewPort/component.go @@ -0,0 +1,82 @@ +package viewport + +import ( + "fmt" + "strings" + + "github.com/charmbracelet/bubbles/key" + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/maniac-en/req/internal/backend/http" +) + +type Viewport struct { + viewport viewport.Model + width int + height int + ready bool + state string +} + +func (v *Viewport) Init() tea.Cmd { + return nil +} + +func (v *Viewport) SetState(state *http.Response) { + var str strings.Builder + + str.WriteString(fmt.Sprintf("Status code: %d\n", state.StatusCode)) + str.WriteString(fmt.Sprintf("ContentType header: %s\n", state.Headers["Content-Type"])) + str.WriteString(fmt.Sprintf("Body: %s\n", state.Body)) + + v.state = str.String() + v.viewport.SetContent(v.state) +} + +func (v Viewport) Update(msg tea.Msg) (Viewport, tea.Cmd) { + var cmd tea.Cmd + var cmds []tea.Cmd + + switch msg := msg.(type) { + + case tea.WindowSizeMsg: + if !v.ready { + v.viewport = viewport.New(msg.Width, msg.Height-10) + v.ready = true + } else { + v.viewport.Width = msg.Width + v.viewport.Height = msg.Height - 10 + } + } + + v.viewport, cmd = v.viewport.Update(msg) + + cmds = append(cmds, cmd) + + return v, tea.Batch(cmds...) +} + +func (v *Viewport) View() string { + if !v.ready { + return "Waiting.." + } + return v.viewport.View() +} + +func (v *Viewport) OnFocus() { +} + +func (v *Viewport) OnBlur() { +} + +func (v *Viewport) Help() []key.Binding { + return []key.Binding{} +} + +func NewViewport() Viewport { + return Viewport{ + viewport: viewport.New(1, 1), + ready: false, + state: "No response received yet :(", + } +} diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 89edd56..f420155 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -17,6 +17,7 @@ import ( componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" + viewport "github.com/maniac-en/req/internal/tui/components/ViewPort" "github.com/maniac-en/req/internal/tui/keybinds" "github.com/maniac-en/req/internal/tui/messages" "github.com/maniac-en/req/internal/tui/styles" @@ -37,6 +38,7 @@ var componentList = []reqFocused{ type RequestView struct { width int focused reqFocused + viewport viewport.Viewport components map[reqFocused]componenttypes.ReqViewComponent index int height int @@ -47,7 +49,6 @@ type RequestView struct { responsePage bool client *http.HTTPManager order int - res *http.Response update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) endpoint endpoints.EndpointEntity } @@ -84,6 +85,10 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { r.width = msg.Width w := r.components[urlInput].GetWidth() r.components[urlInput].SetWidth(r.width - (w + 30)) + r.viewport, cmd = r.viewport.Update(tea.WindowSizeMsg{ + Height: msg.Height, + Width: r.width, + }) case tea.KeyMsg: switch { case key.Matches(msg, keybinds.Keys.Back): @@ -116,10 +121,10 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { r.loading = true res, err := r.client.ExecuteRequest(request) r.loading = false - r.res = res if err != nil { log.Warn("error occurred while trying to send a request", err) } + r.viewport.SetState(res) return r, r.spinner.Tick case key.Matches(msg, keybinds.Keys.Save): for _, val := range r.components { @@ -138,12 +143,13 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { if !r.responsePage { r.components[r.focused], cmd = r.components[r.focused].Update(msg) + } else { + r.viewport, cmd = r.viewport.Update(msg) } cmds = append(cmds, cmd) if r.loading { - log.Info("working") r.spinner, cmd = r.spinner.Update(msg) cmds = append(cmds, cmd) } @@ -173,24 +179,11 @@ func (r *RequestView) shift(next bool) { func (r *RequestView) View() string { if r.responsePage { - if r.res == nil { - return styles.RequestLayout(r.height, r.width)(lipgloss.Place(r.width, r.height-1, lipgloss.Center, lipgloss.Center, "No responses so far")) - } if r.loading { loadingString := fmt.Sprintf("%s Loading...", r.spinner.View()) return styles.RequestLayout(r.height, r.width)(lipgloss.Place(r.width, r.height-1, lipgloss.Center, lipgloss.Center, loadingString)) } - // TODO: use a viewport here PLEASE => https://github.com/charmbracelet/bubbles?tab=readme-ov-file#viewport - responseDetails := []string{ - fmt.Sprintf("Status Code: %d", r.res.StatusCode), - fmt.Sprintf("Body: %s", r.res.Body), - } - for key, val := range r.res.Headers { - for _, item := range val { - responseDetails = append(responseDetails, fmt.Sprintf("%s: %s", key, item)) - } - } - view := styles.ResponseStyle(r.height, r.width)(lipgloss.JoinVertical(lipgloss.Top, responseDetails...)) + view := styles.ResponseStyle(r.height, r.width)(r.viewport.View()) return view } else { views := []string{} @@ -254,6 +247,7 @@ func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) urlInput: urlinput.NewUrlInput(uiConfig), }, focused: methodPicker, + viewport: viewport.NewViewport(), update: update, client: http.NewHTTPManager(), responsePage: false, From 6249917939c4c4ad892049751dc9ecffa6decdb7 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Mon, 2 Mar 2026 01:03:21 +0100 Subject: [PATCH 31/46] bug fixes --- internal/tui/app/model.go | 12 +++++++++++ .../components/OptionsProvider/component.go | 20 ++++++++++++++----- .../tui/components/OptionsProvider/config.go | 1 + internal/tui/components/ViewPort/component.go | 2 +- internal/tui/views/collections-view.go | 1 + internal/tui/views/endpoints-view.go | 7 ++++++- internal/tui/views/request-view.go | 3 +++ 7 files changed, 39 insertions(+), 7 deletions(-) diff --git a/internal/tui/app/model.go b/internal/tui/app/model.go index 511484c..0c2ab00 100644 --- a/internal/tui/app/model.go +++ b/internal/tui/app/model.go @@ -47,6 +47,10 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd var cmds []tea.Cmd switch msg := msg.(type) { + case messages.DeleteItem: + a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) + case messages.ItemAdded: + a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) case tea.WindowSizeMsg: a.height = msg.Height a.width = msg.Width @@ -97,6 +101,14 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.Views[a.focusedView], cmd = a.Views[a.focusedView].Update(msg) cmds = append(cmds, cmd) + // additional update for collections list to update when an ep is added + switch msg.(type) { + case messages.DeleteItem: + a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) + case messages.ItemAdded: + a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) + } + return a, tea.Batch(cmds...) } diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index a2c4134..594f622 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -133,7 +133,17 @@ func (o OptionsProvider[T, U]) GetSelected() Option { Subtext: "", } } - return o.list.SelectedItem().(Option) + + item := o.list.SelectedItem() + + if item == nil { + return Option{ + Name: "No selection", + ID: -1, + Subtext: "", + } + } + return item.(Option) } func (o OptionsProvider[T, U]) IsFiltering() bool { @@ -193,7 +203,7 @@ func initList[T, U any](config *ListConfig[T, U]) list.Model { items := config.ItemMapper(rawItems) - list := list.New(items, config.Delegate(true), 30, 30) + list := list.New(items, config.Delegate(true), 100, 30) // list configuration list.SetFilteringEnabled(config.FilteringEnabled) @@ -213,8 +223,8 @@ func (o *OptionsProvider[T, U]) SetGetItemsFunc(getItems func(context.Context) ( log.Error("error fetching items") } o.list.SetItems(o.itemMapper(items)) - if o.onChangeAction != nil { - o.onChangeAction(o.itemMapper(items)[0].(Option).ID) + if mappedItems := o.itemMapper(items); o.onChangeAction != nil && len(mappedItems) > 0 { + o.onChangeAction(mappedItems[0].(Option).ID) } } @@ -222,7 +232,7 @@ func (o *OptionsProvider[T, U]) SetGetItemsFunc(getItems func(context.Context) ( func NewOptionsProvider[T, U any](config *ListConfig[T, U]) OptionsProvider[T, U] { inputConfig := InputConfig{ CharLimit: 100, - Placeholder: "Add A New Collection...", + Placeholder: config.Placeholder, Width: 22, Prompt: "", KeyMap: InputKeyMaps{ diff --git a/internal/tui/components/OptionsProvider/config.go b/internal/tui/components/OptionsProvider/config.go index eabf0e7..7c8461b 100644 --- a/internal/tui/components/OptionsProvider/config.go +++ b/internal/tui/components/OptionsProvider/config.go @@ -22,6 +22,7 @@ type ListConfig[T, U any] struct { FilteringEnabled bool + Placeholder string Delegate func(bool) list.ItemDelegate KeyMap list.KeyMap AdditionalKeymaps *keybinds.ListKeyMap diff --git a/internal/tui/components/ViewPort/component.go b/internal/tui/components/ViewPort/component.go index 0533bf1..3aa1b3f 100644 --- a/internal/tui/components/ViewPort/component.go +++ b/internal/tui/components/ViewPort/component.go @@ -41,7 +41,7 @@ func (v Viewport) Update(msg tea.Msg) (Viewport, tea.Cmd) { case tea.WindowSizeMsg: if !v.ready { - v.viewport = viewport.New(msg.Width, msg.Height-10) + v.viewport = viewport.New(msg.Width, msg.Height-20) v.ready = true } else { v.viewport.Width = msg.Width diff --git a/internal/tui/views/collections-view.go b/internal/tui/views/collections-view.go index 64ffe4a..5224c6f 100644 --- a/internal/tui/views/collections-view.go +++ b/internal/tui/views/collections-view.go @@ -140,6 +140,7 @@ func NewCollectionsView(collManager *collections.CollectionsManager, endpointsMa } config.AdditionalKeymaps = keybinds config.Source = "collections" + config.Placeholder = "Add a new collection..." return &CollectionsView{ list: optionsProvider.NewOptionsProvider(config), diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 4b28c08..80e13a8 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -69,11 +69,15 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { }) cmds = append(cmds, cmd) case messages.ItemAdded: - e.manager.CreateEndpoint(context.Background(), endpoints.EndpointData{ + ep, err := e.manager.CreateEndpoint(context.Background(), endpoints.EndpointData{ CollectionID: e.collection.ID, Name: msg.Item, Method: "GET", }) + if err != nil { + //TODO: handle this + } + e.requestView.SetState(ep) case messages.RefreshItemsList: e.list.RefreshItems() case messages.ItemEdited: @@ -196,6 +200,7 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin config.ItemMapper = itemMapperEp config.AdditionalKeymaps = keybinds config.Source = "endpoints" + config.Placeholder = "Add a new endpoint..." view.list = optionsProvider.NewOptionsProvider(config) view.focused = listView diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index f420155..74d556d 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -186,6 +186,9 @@ func (r *RequestView) View() string { view := styles.ResponseStyle(r.height, r.width)(r.viewport.View()) return view } else { + if r.endpoint.Name == "" { + return styles.RequestLayout(r.height, r.width)("No Endpoint selected") + } views := []string{} for _, val := range componentList { views = append(views, r.components[val].View()) From 76f6f54208d666c666a483a048e2de3a8e89017b Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Mon, 2 Mar 2026 01:17:56 +0100 Subject: [PATCH 32/46] bug fixes --- internal/tui/components/ViewPort/component.go | 6 +++++- internal/tui/views/endpoints-view.go | 1 + internal/tui/views/request-view.go | 8 +++++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/tui/components/ViewPort/component.go b/internal/tui/components/ViewPort/component.go index 3aa1b3f..e4e45ed 100644 --- a/internal/tui/components/ViewPort/component.go +++ b/internal/tui/components/ViewPort/component.go @@ -7,6 +7,7 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/http" ) @@ -60,7 +61,9 @@ func (v *Viewport) View() string { if !v.ready { return "Waiting.." } - return v.viewport.View() + // FIX: this mess + percentage := int(v.viewport.ScrollPercent() * 100) + return lipgloss.JoinVertical(lipgloss.Top, v.viewport.View(), fmt.Sprintf("%d%%", percentage)) } func (v *Viewport) OnFocus() { @@ -70,6 +73,7 @@ func (v *Viewport) OnBlur() { } func (v *Viewport) Help() []key.Binding { + // FIX: this return []key.Binding{} } diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 80e13a8..50ac843 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -101,6 +101,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { switch { case key.Matches(msg, keybinds.Keys.Back): if e.focused == listView { + e.requestView.SetState(endpoints.EndpointEntity{}) return e, func() tea.Msg { return messages.NavigateToView{ ViewName: Collections, diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 74d556d..eba27bc 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -69,6 +69,7 @@ func (r *RequestView) Help() []key.Binding { keybinds.Keys.Save, keybinds.Keys.SendRequest, } + // FIX: should append responsePage binds when necessary return append(binds, reqViewBinds...) } @@ -249,9 +250,10 @@ func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) methodPicker: methodpicker.NewMethodPicker(mpConfig), urlInput: urlinput.NewUrlInput(uiConfig), }, - focused: methodPicker, - viewport: viewport.NewViewport(), - update: update, + focused: methodPicker, + viewport: viewport.NewViewport(), + update: update, + // FIX: use app context http manager client: http.NewHTTPManager(), responsePage: false, spinner: s, From a09f1343b808719ae52cc6b456c4a5d0775a9bc2 Mon Sep 17 00:00:00 2001 From: Yash Ranjan Date: Fri, 28 Aug 2026 18:18:47 +0200 Subject: [PATCH 33/46] transfer --- internal/tui/components/ViewPort/component.go | 28 +++++++++++++++---- internal/tui/views/request-view.go | 22 +++++++++------ 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/internal/tui/components/ViewPort/component.go b/internal/tui/components/ViewPort/component.go index e4e45ed..b30438c 100644 --- a/internal/tui/components/ViewPort/component.go +++ b/internal/tui/components/ViewPort/component.go @@ -9,6 +9,7 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/http" + "github.com/maniac-en/req/internal/tui/keybinds" ) type Viewport struct { @@ -26,9 +27,9 @@ func (v *Viewport) Init() tea.Cmd { func (v *Viewport) SetState(state *http.Response) { var str strings.Builder - str.WriteString(fmt.Sprintf("Status code: %d\n", state.StatusCode)) - str.WriteString(fmt.Sprintf("ContentType header: %s\n", state.Headers["Content-Type"])) - str.WriteString(fmt.Sprintf("Body: %s\n", state.Body)) + fmt.Fprintf(&str, "Status code: %d\n", state.StatusCode) + fmt.Fprintf(&str, "ContentType header: %s\n", state.Headers["Content-Type"]) + fmt.Fprintf(&str, "Body: %s\n", state.Body) v.state = str.String() v.viewport.SetContent(v.state) @@ -43,6 +44,8 @@ func (v Viewport) Update(msg tea.Msg) (Viewport, tea.Cmd) { case tea.WindowSizeMsg: if !v.ready { v.viewport = viewport.New(msg.Width, msg.Height-20) + v.width = msg.Width + v.height = msg.Height v.ready = true } else { v.viewport.Width = msg.Width @@ -62,8 +65,18 @@ func (v *Viewport) View() string { return "Waiting.." } // FIX: this mess - percentage := int(v.viewport.ScrollPercent() * 100) - return lipgloss.JoinVertical(lipgloss.Top, v.viewport.View(), fmt.Sprintf("%d%%", percentage)) + str := fmt.Sprintf(" %d%% ", int(v.viewport.ScrollPercent()*100)) + + padding := v.height - v.viewport.Height - 7 + + return lipgloss.JoinVertical(lipgloss.Left, + v.viewport.View(), + lipgloss.NewStyle(). + Width(v.width). + PaddingTop(padding). + Align(lipgloss.Right). + Render(str), + ) } func (v *Viewport) OnFocus() { @@ -74,7 +87,10 @@ func (v *Viewport) OnBlur() { func (v *Viewport) Help() []key.Binding { // FIX: this - return []key.Binding{} + return []key.Binding{ + keybinds.Keys.Down, + keybinds.Keys.Up, + } } func NewViewport() Viewport { diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index eba27bc..13fd54c 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -62,15 +62,21 @@ func (r *RequestView) Name() string { } func (r *RequestView) Help() []key.Binding { - binds := r.components[r.focused].Help() - reqViewBinds := []key.Binding{ - keybinds.Keys.Prev, - keybinds.Keys.Next, - keybinds.Keys.Save, - keybinds.Keys.SendRequest, + var reqViewBinds []key.Binding + if r.responsePage { + reqViewBinds = r.viewport.Help() + } else { + binds := r.components[r.focused].Help() + reqViewBinds = []key.Binding{ + keybinds.Keys.Prev, + keybinds.Keys.Next, + keybinds.Keys.Save, + keybinds.Keys.SendRequest, + } + reqViewBinds = append(binds, reqViewBinds...) } // FIX: should append responsePage binds when necessary - return append(binds, reqViewBinds...) + return reqViewBinds } func (r *RequestView) GetFooterSegment() string { @@ -88,7 +94,7 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { r.components[urlInput].SetWidth(r.width - (w + 30)) r.viewport, cmd = r.viewport.Update(tea.WindowSizeMsg{ Height: msg.Height, - Width: r.width, + Width: r.width - 4, }) case tea.KeyMsg: switch { From b23bdb9f32d5d815bdb92e638e04e4b1cf90b290 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 28 Aug 2026 19:32:57 +0200 Subject: [PATCH 34/46] feat: Integrate dedicated request view workflow - Adds and wires up the dedicated request view to the application model. - Refactors EndpointsView to streamline navigation to the request view. - Simplifies item delegate logic in endpoints-view-helper.go by adopting default styles. - Updates RequestView state initialization to load endpoints by ID. --- internal/tui/app/model.go | 6 ++ internal/tui/styles/collections-styles.go | 1 + internal/tui/styles/request-styles.go | 3 +- internal/tui/views/endpoints-view-helper.go | 44 ++------------- internal/tui/views/endpoints-view.go | 61 +++++++-------------- internal/tui/views/request-view.go | 17 ++++-- 6 files changed, 46 insertions(+), 86 deletions(-) diff --git a/internal/tui/app/model.go b/internal/tui/app/model.go index 0c2ab00..a0080d8 100644 --- a/internal/tui/app/model.go +++ b/internal/tui/app/model.go @@ -21,6 +21,7 @@ type ViewName string const ( Collections ViewName = "collections" Endpoints ViewName = "endpoints" + Request ViewName = "request" ) type Heading struct { @@ -86,6 +87,7 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.focusedView = ViewName(msg.ViewName) a.Views[a.focusedView].OnFocus() return a, nil + case messages.ShowError: log.Error("user operation failed", "error", msg.Message) a.errorMsg = msg.Message @@ -196,9 +198,13 @@ func NewAppModel(ctx *Context) AppModel { help: help.New(), keys: appKeybinds, } + + epUpdateFunc := model.ctx.Endpoints.UpdateEndpoint + model.Views = map[ViewName]views.ViewInterface{ Collections: views.NewCollectionsView(model.ctx.Collections, model.ctx.Endpoints, 1), Endpoints: views.NewEndpointsView(model.ctx.Endpoints, 2), + Request: views.NewRequestView(model.ctx.Endpoints, epUpdateFunc, 3), } return model } diff --git a/internal/tui/styles/collections-styles.go b/internal/tui/styles/collections-styles.go index b4d4393..a8d8614 100644 --- a/internal/tui/styles/collections-styles.go +++ b/internal/tui/styles/collections-styles.go @@ -7,4 +7,5 @@ var ( SelectedActiveListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) + // Grad = lipg ) diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index af44b01..d712c2d 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -8,7 +8,6 @@ func RequestLayout(height, width int) func(...string) string { Width(width). Margin(0, 0, 0, 5). Padding(1, 0, 0, 10). - Border(lipgloss.NormalBorder(), false, false, false, true). Render } @@ -27,7 +26,7 @@ func UrlInputStyle(active bool) lipgloss.Style { } func ResponseStyle(height, width int) func(...string) string { - return lipgloss.NewStyle().Margin(0, 0, 0, 5).Border(lipgloss.NormalBorder(), false, false, false, true).Padding(1, 0, 0, 5).Height(height).Width(width).Render + return lipgloss.NewStyle().Margin(0, 0, 0, 5).Padding(1, 0, 0, 5).Height(height).Width(width).Render } var ( diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go index 7a4e659..d44d704 100644 --- a/internal/tui/views/endpoints-view-helper.go +++ b/internal/tui/views/endpoints-view-helper.go @@ -1,53 +1,19 @@ package views import ( - "fmt" - "io" - "strings" - "github.com/charmbracelet/bubbles/list" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/maniac-en/req/internal/tui/components/OptionsProvider" "github.com/maniac-en/req/internal/tui/styles" ) func createEndpointsDelegate(active bool) list.ItemDelegate { - d := epItemDelegate{} - + d := list.NewDefaultDelegate() if active { - d.SelectedTitleStyle = styles.SelectedActiveListStyle + d.Styles.SelectedTitle = styles.SelectedActiveListStyle } else { - d.SelectedTitleStyle = styles.SelectedInactiveListStyle - } - d.UnselectedTitleStyle = styles.UnselectedListStyle + d.Styles.SelectedTitle = styles.SelectedInactiveListStyle - return d -} - -type epItemDelegate struct { - SelectedTitleStyle lipgloss.Style - UnselectedTitleStyle lipgloss.Style -} - -func (e epItemDelegate) Height() int { return 1 } -func (e epItemDelegate) Spacing() int { return 1 } -func (e epItemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } -func (e epItemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { - item, ok := listItem.(optionsProvider.Option) - - if !ok { - return - } - - str := fmt.Sprintf("%s %s", item.Subtext, item.Name) - - fn := e.UnselectedTitleStyle.Render - if index == m.Index() { - fn = func(s ...string) string { - return e.SelectedTitleStyle.Render(strings.Join(s, " ")) - } } + d.Styles.SelectedDesc = styles.UnselectedListStyle - fmt.Fprint(w, fn(str)) + return d } diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 50ac843..a4b1509 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -19,19 +19,17 @@ import ( type epFocused string const ( - listView = "listview" - requestView = "requestview" + listView = "listview" ) type EndpointsView struct { - height int - focused epFocused - collection optionsProvider.Option - width int - order int - list optionsProvider.OptionsProvider[endpoints.EndpointEntity, database.Endpoint] - requestView ViewInterface - manager *endpoints.EndpointsManager + height int + focused epFocused + collection optionsProvider.Option + width int + order int + list optionsProvider.OptionsProvider[endpoints.EndpointEntity, database.Endpoint] + manager *endpoints.EndpointsManager } func (e *EndpointsView) Init() tea.Cmd { @@ -43,11 +41,7 @@ func (e *EndpointsView) Name() string { } func (e *EndpointsView) Help() []key.Binding { - if e.focused == listView { - return e.list.Help() - } else { - return e.requestView.Help() - } + return e.list.Help() } func (e *EndpointsView) GetFooterSegment() string { @@ -62,14 +56,9 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.height = msg.Height e.width = msg.Width e.list, cmd = e.list.Update(msg) - _, w := e.list.GetSize() - e.requestView, cmd = e.requestView.Update(tea.WindowSizeMsg{ - Height: msg.Height, - Width: msg.Width - w, - }) cmds = append(cmds, cmd) case messages.ItemAdded: - ep, err := e.manager.CreateEndpoint(context.Background(), endpoints.EndpointData{ + _, err := e.manager.CreateEndpoint(context.Background(), endpoints.EndpointData{ CollectionID: e.collection.ID, Name: msg.Item, Method: "GET", @@ -77,7 +66,6 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { if err != nil { //TODO: handle this } - e.requestView.SetState(ep) case messages.RefreshItemsList: e.list.RefreshItems() case messages.ItemEdited: @@ -87,13 +75,19 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { e.list.RefreshItems() case messages.ChooseItem[optionsProvider.Option]: e.list.OnBlur() - e.requestView.OnFocus() - e.focused = requestView + return e, func() tea.Msg { + return messages.NavigateToView{ + ViewName: "request", + Target: MainModel, + Data: msg.Item.ID, + } + } case messages.NavigateToView: if msg.Target != Endpoints { break } - e.requestView.OnBlur() + // FIX: idek + // e.requestView.OnBlur() e.focused = listView e.list.OnFocus() @@ -101,7 +95,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { switch { case key.Matches(msg, keybinds.Keys.Back): if e.focused == listView { - e.requestView.SetState(endpoints.EndpointEntity{}) + // e.requestView.SetState(endpoints.EndpointEntity{}) return e, func() tea.Msg { return messages.NavigateToView{ ViewName: Collections, @@ -115,8 +109,6 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { switch e.focused { case listView: e.list, cmd = e.list.Update(msg) - case requestView: - e.requestView, cmd = e.requestView.Update(msg) } cmds = append(cmds, cmd) @@ -125,7 +117,7 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { } func (e *EndpointsView) View() string { - return lipgloss.JoinHorizontal(lipgloss.Top, e.list.View(), e.requestView.View()) + return e.list.View() } func (e *EndpointsView) OnFocus() { @@ -182,17 +174,6 @@ func NewEndpointsView(epManager *endpoints.EndpointsManager, order int) *Endpoin keybinds := keybinds.NewListKeyMap() config := defaultListConfig[endpoints.EndpointEntity, database.Endpoint](keybinds, createEndpointsDelegate) - epUpdateFunc := epManager.UpdateEndpoint - view.requestView = NewRequestView(epUpdateFunc) - - config.OnChangeAction = func(id int64) { - epDetails, err := epManager.Read(context.Background(), id) - if err != nil { - // TODO: what do we do here? prob throw a crash ig? - } - view.requestView.SetState(epDetails) - } - epListFunc := func(ctx context.Context) ([]endpoints.EndpointEntity, error) { return epManager.ListByCollection(ctx, view.collection.ID) } diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 13fd54c..98cd792 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -41,6 +41,7 @@ type RequestView struct { viewport viewport.Viewport components map[reqFocused]componenttypes.ReqViewComponent index int + epManager *endpoints.EndpointsManager height int loading bool help help.Model @@ -210,7 +211,11 @@ func (r *RequestView) View() string { func (r *RequestView) SetState(items ...any) error { if len(items) == 1 { - if ep, ok := items[0].(endpoints.EndpointEntity); ok { + if id, ok := items[0].(int64); ok { + ep, err := r.epManager.Read(context.Background(), id) + if err != nil { + // FIX: do something over here + } r.endpoint = ep for _, val := range componentList { r.components[val].SetState(ep) @@ -244,7 +249,7 @@ func methodItemMapper(items []string) []list.Item { return listItems } -func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error)) *RequestView { +func NewRequestView(epManager *endpoints.EndpointsManager, update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error), order int) *RequestView { mpConfig := createMethodPickerConfig() uiConfig := createURLInputConfig() @@ -256,9 +261,11 @@ func NewRequestView(update func(context.Context, int64, endpoints.EndpointData) methodPicker: methodpicker.NewMethodPicker(mpConfig), urlInput: urlinput.NewUrlInput(uiConfig), }, - focused: methodPicker, - viewport: viewport.NewViewport(), - update: update, + epManager: epManager, + focused: methodPicker, + viewport: viewport.NewViewport(), + order: order, + update: update, // FIX: use app context http manager client: http.NewHTTPManager(), responsePage: false, From 040075a26c68ffb2c6a830509c27ff1385878e4b Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 28 Aug 2026 21:47:02 +0200 Subject: [PATCH 35/46] refactor: Improve TUI architecture and state handling - Refactors AppModel update logic to correctly handle window resizing across all views. - Updates RequestView and AppModel initialization to use a structured RequestViewConfig. - Improves error handling in main and run functions for safer process termination. - Adds support for displaying HTTP response data and errors in the RequestView. --- internal/tui/app/model.go | 20 ++++++------ internal/tui/messages/messages.go | 7 +++++ internal/tui/views/endpoints-view.go | 8 +++++ internal/tui/views/request-view.go | 47 +++++++++++++++++++--------- main.go | 14 +++++++-- 5 files changed, 68 insertions(+), 28 deletions(-) diff --git a/internal/tui/app/model.go b/internal/tui/app/model.go index a0080d8..70626a7 100644 --- a/internal/tui/app/model.go +++ b/internal/tui/app/model.go @@ -55,11 +55,10 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: a.height = msg.Height a.width = msg.Width - for key, _ := range a.Views { + for key := range a.Views { a.Views[key], cmd = a.Views[key].Update(tea.WindowSizeMsg{Height: a.AvailableHeight(), Width: msg.Width}) cmds = append(cmds, cmd) } - cmds = append(cmds, cmd) return a, tea.Batch(cmds...) case messages.ChooseItem[optionsProvider.Option]: switch msg.Source { @@ -103,14 +102,6 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.Views[a.focusedView], cmd = a.Views[a.focusedView].Update(msg) cmds = append(cmds, cmd) - // additional update for collections list to update when an ep is added - switch msg.(type) { - case messages.DeleteItem: - a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) - case messages.ItemAdded: - a.Views[Collections], cmd = a.Views[Collections].Update(messages.RefreshItemsList{}) - } - return a, tea.Batch(cmds...) } @@ -201,10 +192,17 @@ func NewAppModel(ctx *Context) AppModel { epUpdateFunc := model.ctx.Endpoints.UpdateEndpoint + reqCfg := views.RequestViewConfig{ + EpManager: model.ctx.Endpoints, + Update: epUpdateFunc, + Order: 3, + Client: model.ctx.HTTP, + } + model.Views = map[ViewName]views.ViewInterface{ Collections: views.NewCollectionsView(model.ctx.Collections, model.ctx.Endpoints, 1), Endpoints: views.NewEndpointsView(model.ctx.Endpoints, 2), - Request: views.NewRequestView(model.ctx.Endpoints, epUpdateFunc, 3), + Request: views.NewRequestView(reqCfg), } return model } diff --git a/internal/tui/messages/messages.go b/internal/tui/messages/messages.go index 21ad3b9..1077f33 100644 --- a/internal/tui/messages/messages.go +++ b/internal/tui/messages/messages.go @@ -1,5 +1,7 @@ package messages +import "github.com/maniac-en/req/internal/backend/http" + type ItemAdded struct { Item string } @@ -25,6 +27,11 @@ type NavigateToView struct { Data interface{} } +type Response struct { + Data *http.Response + Err error +} + type RefreshItemsList struct{} type ShowError struct { diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index a4b1509..3171bf6 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -8,12 +8,20 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" +<<<<<<< HEAD "github.com/charmbracelet/lipgloss" "github.com/yashranjan1/relay/internal/backend/database" "github.com/yashranjan1/relay/internal/backend/endpoints" optionsProvider "github.com/yashranjan1/relay/internal/tui/components/OptionsProvider" "github.com/yashranjan1/relay/internal/tui/keybinds" "github.com/yashranjan1/relay/internal/tui/messages" +======= + "github.com/maniac-en/req/internal/backend/database" + "github.com/maniac-en/req/internal/backend/endpoints" + optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" + "github.com/maniac-en/req/internal/tui/keybinds" + "github.com/maniac-en/req/internal/tui/messages" +>>>>>>> 2463b9c (refactor: Improve TUI architecture and state handling) ) type epFocused string diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index 98cd792..c7dcf6d 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -13,7 +13,6 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/maniac-en/req/internal/backend/endpoints" "github.com/maniac-en/req/internal/backend/http" - "github.com/maniac-en/req/internal/log" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" @@ -97,6 +96,12 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { Height: msg.Height, Width: r.width - 4, }) + case messages.Response: + if msg.Err != nil { + // TODO: do something here idek + } + r.SetState(msg.Data) + r.loading = false case tea.KeyMsg: switch { case key.Matches(msg, keybinds.Keys.Back): @@ -127,13 +132,18 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { } r.responsePage = true r.loading = true - res, err := r.client.ExecuteRequest(request) - r.loading = false - if err != nil { - log.Warn("error occurred while trying to send a request", err) + sendMsg := func() tea.Msg { + res, err := r.client.ExecuteRequest(request) + if err != nil { + return messages.Response{ + Err: err, + } + } + return messages.Response{ + Data: res, + } } - r.viewport.SetState(res) - return r, r.spinner.Tick + return r, tea.Batch(r.spinner.Tick, sendMsg) case key.Matches(msg, keybinds.Keys.Save): for _, val := range r.components { r.endpoint = val.UpdateState(r.endpoint) @@ -249,7 +259,14 @@ func methodItemMapper(items []string) []list.Item { return listItems } -func NewRequestView(epManager *endpoints.EndpointsManager, update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error), order int) *RequestView { +type RequestViewConfig struct { + EpManager *endpoints.EndpointsManager + Update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) + Order int + Client *http.HTTPManager +} + +func NewRequestView(cfg RequestViewConfig) *RequestView { mpConfig := createMethodPickerConfig() uiConfig := createURLInputConfig() @@ -261,13 +278,13 @@ func NewRequestView(epManager *endpoints.EndpointsManager, update func(context.C methodPicker: methodpicker.NewMethodPicker(mpConfig), urlInput: urlinput.NewUrlInput(uiConfig), }, - epManager: epManager, - focused: methodPicker, - viewport: viewport.NewViewport(), - order: order, - update: update, - // FIX: use app context http manager - client: http.NewHTTPManager(), + + epManager: cfg.EpManager, + focused: methodPicker, + viewport: viewport.NewViewport(), + order: cfg.Order, + update: cfg.Update, + client: cfg.Client, responsePage: false, spinner: s, } diff --git a/main.go b/main.go index eb7814f..4af45bd 100644 --- a/main.go +++ b/main.go @@ -114,6 +114,13 @@ func runMigrations() error { } func main() { + if err := run(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func run() error { // initialize paths first if err := initPaths(); err != nil { fmt.Fprintf(os.Stderr, "Failed to initialize: %v\n", err) @@ -139,11 +146,13 @@ func main() { // run database migrations if err := runMigrations(); err != nil { - log.Fatal("failed to run migrations", "error", err) + return fmt.Errorf("failed to run migrations", "error", err) } // create database client and managers db := database.New(DB) + defer DB.Close() + collectionsManager := collections.NewCollectionsManager(db) endpointsManager := endpoints.NewEndpointsManager(db) httpManager := http.NewHTTPManager() @@ -174,6 +183,7 @@ func main() { // Entry point for UI program := tea.NewProgram(app.NewAppModel(appContext), tea.WithAltScreen()) if _, err := program.Run(); err != nil { - log.Fatal("Fatal error:", err) + return fmt.Errorf("fatal error: %w", err) } + return nil } From d303ab14328e2d04f13e74b381c05c2312db18fb Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Fri, 28 Aug 2026 21:53:04 +0200 Subject: [PATCH 36/46] chore: remove dummy data population setup - The automatic generation of demo API endpoints and collections has been removed. - This cleans up the main.go startup routine and removes the demo package dependency. - This makes the application startup flow more minimal and reduces reliance on hardcoded demo data. --- main.go | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/main.go b/main.go index 4af45bd..0770a8e 100644 --- a/main.go +++ b/main.go @@ -12,16 +12,15 @@ import ( "runtime/debug" tea "github.com/charmbracelet/bubbletea" + _ "github.com/mattn/go-sqlite3" + "github.com/pressly/goose/v3" "github.com/yashranjan1/relay/internal/backend/collections" "github.com/yashranjan1/relay/internal/backend/database" - "github.com/yashranjan1/relay/internal/backend/demo" "github.com/yashranjan1/relay/internal/backend/endpoints" "github.com/yashranjan1/relay/internal/backend/history" "github.com/yashranjan1/relay/internal/backend/http" "github.com/yashranjan1/relay/internal/log" "github.com/yashranjan1/relay/internal/tui/app" - _ "github.com/mattn/go-sqlite3" - "github.com/pressly/goose/v3" ) // Embed migration files into the binary @@ -167,15 +166,6 @@ func run() error { getVersion(), ) - // populate dummy data for demo - demoGenerator := demo.NewDemoGenerator(collectionsManager, endpointsManager) - dummyDataCreated, err := demoGenerator.PopulateDummyData(context.Background()) - if err != nil { - log.Error("failed to populate dummy data", "error", err) - } else if dummyDataCreated { - // appContext.SetDummyDataCreated(true) - } - log.Info("application initialized", "components", []string{"database", "collections", "endpoints", "http", "history", "logging", "demo"}) log.Debug("configuration loaded", "collections_manager", collectionsManager != nil, "endpoints", endpointsManager != nil, "database", db != nil, "http_manager", httpManager != nil, "history_manager", historyManager != nil) log.Info("application started successfully") From 8100247f317c3cbe39c11c4a54d4f8983c62c111 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Sat, 29 Aug 2026 22:02:26 +0200 Subject: [PATCH 37/46] feat: Add configuration loading and theme system - Implements config loading from user-specified TOML files. - Centralizes TUI styling by introducing a theme system (Dark, Light, Cyberpunk, etc.). - Refactors views to improve focus/blur handling and state consistency across components. - Updates core views and components to utilize the new themed styles and state patterns. --- go.mod | 1 + go.sum | 2 + internal/config/config.go | 49 +++++++++++++++ internal/config/types.go | 16 +++++ internal/tui/app/model.go | 17 ++---- .../components/OptionsProvider/component.go | 12 +++- internal/tui/styles/app-styles.go | 24 ++++++-- internal/tui/styles/collections-styles.go | 11 +++- internal/tui/styles/colors.go | 60 +++++++++++++++---- internal/tui/styles/functions.go | 2 +- internal/tui/styles/request-styles.go | 13 ++-- internal/tui/styles/theme.go | 33 ++++++++++ internal/tui/views/collections-view-helper.go | 2 +- internal/tui/views/collections-view.go | 31 ++++++---- internal/tui/views/endpoints-view-helper.go | 2 +- internal/tui/views/endpoints-view.go | 19 ++---- internal/tui/views/request-view.go | 16 +++-- internal/tui/views/types.go | 8 ++- main.go | 8 ++- 19 files changed, 254 insertions(+), 72 deletions(-) create mode 100644 internal/config/config.go create mode 100644 internal/config/types.go create mode 100644 internal/tui/styles/theme.go diff --git a/go.mod b/go.mod index e0e2d10..c3ae6f7 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/charmbracelet/bubbletea v1.3.6 github.com/charmbracelet/lipgloss v1.1.0 github.com/mattn/go-sqlite3 v1.14.29 + github.com/pelletier/go-toml/v2 v2.4.3 github.com/pressly/goose/v3 v3.24.3 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index 8dbf694..4090e98 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= +github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pressly/goose/v3 v3.24.3 h1:DSWWNwwggVUsYZ0X2VitiAa9sKuqtBfe+Jr9zFGwWlM= diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..4ab7bfa --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,49 @@ +package config + +import ( + "errors" + "os" + "path" + + "github.com/maniac-en/req/internal/log" + "github.com/maniac-en/req/internal/tui/styles" + "github.com/pelletier/go-toml/v2" +) + +func LoadConfig() error { + configDir, err := os.UserConfigDir() + + if err != nil { + log.Error("Could not load user config dir") + return errors.New("Could not load user config dir") + } + path := path.Join(configDir, "req", "config.toml") + + byteData, err := os.ReadFile(path) + if err != nil { + log.Error("Could not load config") + initDefaults() + return nil + } + + var config Config + + toml.Unmarshal(byteData, &config) + + theme, ok := styles.ThemeMap[config.Theme.Name] + + if !ok { + theme = styles.ThemeMap["default"] + } + + styles.SetTheme(theme) + // styles.SetTheme() + + return nil +} + +func initDefaults() { + theme := styles.ThemeMap["default"] + log.Info("setting default config") + styles.SetTheme(theme) +} diff --git a/internal/config/types.go b/internal/config/types.go new file mode 100644 index 0000000..73255d4 --- /dev/null +++ b/internal/config/types.go @@ -0,0 +1,16 @@ +package config + +type Theme struct { +} + +type ThemeType struct { + Name string + Overrides Theme +} + +type ThemeOverrides struct { +} + +type Config struct { + Theme ThemeType `toml:"Theme"` +} diff --git a/internal/tui/app/model.go b/internal/tui/app/model.go index 70626a7..708f9d8 100644 --- a/internal/tui/app/model.go +++ b/internal/tui/app/model.go @@ -9,7 +9,6 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/yashranjan1/relay/internal/log" - optionsProvider "github.com/yashranjan1/relay/internal/tui/components/OptionsProvider" "github.com/yashranjan1/relay/internal/tui/keybinds" "github.com/yashranjan1/relay/internal/tui/messages" "github.com/yashranjan1/relay/internal/tui/styles" @@ -60,16 +59,6 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { cmds = append(cmds, cmd) } return a, tea.Batch(cmds...) - case messages.ChooseItem[optionsProvider.Option]: - switch msg.Source { - case "collections": - return a, func() tea.Msg { - return messages.NavigateToView{ - ViewName: string(Endpoints), - Data: msg.Item, - } - } - } case messages.NavigateToView: if msg.Data != nil { err := a.Views[ViewName(msg.ViewName)].SetState(msg.Data) @@ -84,8 +73,10 @@ func (a AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { a.Views[a.focusedView].OnBlur() a.focusedView = ViewName(msg.ViewName) - a.Views[a.focusedView].OnFocus() - return a, nil + cmd = a.Views[a.focusedView].OnFocus() + cmds = append(cmds, cmd) + + return a, tea.Batch(cmds...) case messages.ShowError: log.Error("user operation failed", "error", msg.Message) diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index 594f622..35b225b 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -110,6 +110,16 @@ func (o OptionsProvider[T, U]) Update(msg tea.Msg) (OptionsProvider[T, U], tea.C return o, tea.Batch(cmds...) } +func (o *OptionsProvider[T, U]) UpdateState() tea.Cmd { + rawItems, err := o.getItems(context.Background()) + if err != nil { + // FIX: add err handling + } + + items := o.itemMapper(rawItems) + return o.list.SetItems(items) +} + func (o OptionsProvider[T, U]) View() string { if o.focused == textComponent { return lipgloss.JoinVertical(lipgloss.Left, o.list.View(), o.input.View()) @@ -118,11 +128,9 @@ func (o OptionsProvider[T, U]) View() string { } func (o *OptionsProvider[T, U]) OnFocus() { - o.list.SetDelegate(o.delegateGenner(true)) } func (o *OptionsProvider[T, U]) OnBlur() { - o.list.SetDelegate(o.delegateGenner(false)) } func (o OptionsProvider[T, U]) GetSelected() Option { diff --git a/internal/tui/styles/app-styles.go b/internal/tui/styles/app-styles.go index 365ef5d..bf89ec3 100644 --- a/internal/tui/styles/app-styles.go +++ b/internal/tui/styles/app-styles.go @@ -5,13 +5,25 @@ import ( ) var ( - footerNameStyle = lipgloss.NewStyle().Bold(true).Background(footerNameBG) - footerNameBGStyle = lipgloss.NewStyle().Background(footerNameBG).Padding(0, 3, 0) - FooterSegmentStyle = lipgloss.NewStyle().Background(footerSegmentBG).PaddingLeft(2).Foreground(footerSegmentFG) - FooterVersionStyle = lipgloss.NewStyle().Background(footerSegmentBG).AlignHorizontal(lipgloss.Right).PaddingRight(2).Foreground(footerSegmentFG) + footerNameStyle = lipgloss.NewStyle().Bold(true).Background(AppTheme.FooterNameBG) + footerNameBGStyle = lipgloss.NewStyle().Background(AppTheme.FooterNameBG).Padding(0, 3, 0) + FooterSegmentStyle = lipgloss.NewStyle().Background(AppTheme.FooterSegmentBG).PaddingLeft(2).Foreground(AppTheme.FooterSegmentFG) + FooterVersionStyle = lipgloss.NewStyle().Background(AppTheme.FooterSegmentBG).AlignHorizontal(lipgloss.Right).PaddingRight(2).Foreground(AppTheme.FooterSegmentFG) TabHeadingInactive = lipgloss.NewStyle().Width(25).AlignHorizontal(lipgloss.Center).Border(lipgloss.NormalBorder(), false, false, false, true) - TabHeadingActive = lipgloss.NewStyle().Background(accent).Foreground(headingForeground).Width(25).AlignHorizontal(lipgloss.Center).Border(lipgloss.NormalBorder(), false, false, false, true) + TabHeadingActive = lipgloss.NewStyle().Background(AppTheme.Accent).Foreground(AppTheme.HeadingForeground).Width(25).AlignHorizontal(lipgloss.Center).Border(lipgloss.NormalBorder(), false, false, false, true) HelpStyle = lipgloss.NewStyle().Padding(1, 0, 1, 2) - AppHelpStyle = lipgloss.NewStyle().Padding(1, 0).Foreground(helpFG) + AppHelpStyle = lipgloss.NewStyle().Padding(1, 0).Foreground(AppTheme.HelpFG) ErrorBarStyle = lipgloss.NewStyle().Background(lipgloss.Color("#FF0000")).Foreground(lipgloss.Color("#FFFFFF")).Padding(0, 1) ) + +func initAppStyles() { + footerNameStyle = lipgloss.NewStyle().Bold(true).Background(AppTheme.FooterNameBG) + footerNameBGStyle = lipgloss.NewStyle().Background(AppTheme.FooterNameBG).Padding(0, 3, 0) + FooterSegmentStyle = lipgloss.NewStyle().Background(AppTheme.FooterSegmentBG).PaddingLeft(2).Foreground(AppTheme.FooterSegmentFG) + FooterVersionStyle = lipgloss.NewStyle().Background(AppTheme.FooterSegmentBG).AlignHorizontal(lipgloss.Right).PaddingRight(2).Foreground(AppTheme.FooterSegmentFG) + TabHeadingInactive = lipgloss.NewStyle().Width(25).AlignHorizontal(lipgloss.Center).Border(lipgloss.NormalBorder(), false, false, false, true) + TabHeadingActive = lipgloss.NewStyle().Background(AppTheme.Accent).Foreground(AppTheme.HeadingForeground).Width(25).AlignHorizontal(lipgloss.Center).Border(lipgloss.NormalBorder(), false, false, false, true) + HelpStyle = lipgloss.NewStyle().Padding(1, 0, 1, 2) + AppHelpStyle = lipgloss.NewStyle().Padding(1, 0).Foreground(AppTheme.HelpFG) + ErrorBarStyle = lipgloss.NewStyle().Background(lipgloss.Color("#FF0000")).Foreground(lipgloss.Color("#FFFFFF")).Padding(0, 1) +} diff --git a/internal/tui/styles/collections-styles.go b/internal/tui/styles/collections-styles.go index a8d8614..daf234e 100644 --- a/internal/tui/styles/collections-styles.go +++ b/internal/tui/styles/collections-styles.go @@ -4,8 +4,17 @@ import "github.com/charmbracelet/lipgloss" var ( SelectedInactiveListStyle = lipgloss.NewStyle().PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true) - SelectedActiveListStyle = lipgloss.NewStyle().Foreground(accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(accent) + SelectedActiveListStyle = lipgloss.NewStyle().Foreground(AppTheme.Accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(AppTheme.Accent) UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) // Grad = lipg ) + +func initCollectionStyles() { + + SelectedInactiveListStyle = lipgloss.NewStyle().PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true) + SelectedActiveListStyle = lipgloss.NewStyle().Foreground(AppTheme.Accent).PaddingLeft(1).Border(lipgloss.NormalBorder(), false, false, false, true).BorderForeground(AppTheme.Accent) + UnselectedListStyle = lipgloss.NewStyle().PaddingLeft(2) + InputStyle = lipgloss.NewStyle().Padding(1, 2).Border(lipgloss.NormalBorder(), false, false, false, true).Margin(1, 0) + // Grad = lipg +} diff --git a/internal/tui/styles/colors.go b/internal/tui/styles/colors.go index 9cb8232..12f6f05 100644 --- a/internal/tui/styles/colors.go +++ b/internal/tui/styles/colors.go @@ -2,13 +2,53 @@ package styles import "github.com/charmbracelet/lipgloss" -var ( - footerNameBG = lipgloss.Color("#1a1a1a") - footerNameFGFrom = lipgloss.Color("#41A0AE") - footerNameFGTo = lipgloss.Color("#77F07F") - accent = lipgloss.Color("#77F07F") - headingForeground = lipgloss.Color("#000000") - footerSegmentBG = lipgloss.Color("#262626") - footerSegmentFG = lipgloss.Color("#656565") - helpFG = lipgloss.Color("#3C3C3C") -) +var ThemeMap = map[string]Theme{ + "default": DefaultTheme, + "cyberpunk": CyberpunkTheme, + "light-paper": LightPaperTheme, + "forest": ForestTheme, +} + +var DefaultTheme = Theme{ + FooterNameBG: lipgloss.Color("#1a1a1a"), + FooterNameFGFrom: lipgloss.Color("#41A0AE"), + FooterNameFGTo: lipgloss.Color("#77F07F"), + Accent: lipgloss.Color("#77F07F"), + HeadingForeground: lipgloss.Color("#000000"), + FooterSegmentBG: lipgloss.Color("#262626"), + FooterSegmentFG: lipgloss.Color("#656565"), + HelpFG: lipgloss.Color("#3C3C3C"), +} + +var LightPaperTheme = Theme{ + FooterNameBG: lipgloss.Color("#E5E5E0"), + FooterNameFGFrom: lipgloss.Color("#006680"), + FooterNameFGTo: lipgloss.Color("#2AA198"), + Accent: lipgloss.Color("#2AA198"), + HeadingForeground: lipgloss.Color("#073642"), + FooterSegmentBG: lipgloss.Color("#D3D3CD"), + FooterSegmentFG: lipgloss.Color("#586E75"), + HelpFG: lipgloss.Color("#93A1A1"), +} + +var ForestTheme = Theme{ + FooterNameBG: lipgloss.Color("#18201E"), + FooterNameFGFrom: lipgloss.Color("#40A02B"), // Rich forest green + FooterNameFGTo: lipgloss.Color("#179299"), // Deep teal + Accent: lipgloss.Color("#40A02B"), // Darker green with much higher contrast + HeadingForeground: lipgloss.Color("#E6E9EF"), + FooterSegmentBG: lipgloss.Color("#25322F"), + FooterSegmentFG: lipgloss.Color("#97B6AC"), + HelpFG: lipgloss.Color("#4F6C64"), +} + +var CyberpunkTheme = Theme{ + FooterNameBG: lipgloss.Color("#181425"), + FooterNameFGFrom: lipgloss.Color("#FF2A6D"), + FooterNameFGTo: lipgloss.Color("#05D9E8"), + Accent: lipgloss.Color("#05D9E8"), + HeadingForeground: lipgloss.Color("#FFFFFF"), + FooterSegmentBG: lipgloss.Color("#241734"), + FooterSegmentFG: lipgloss.Color("#8A78A5"), + HelpFG: lipgloss.Color("#62447D"), +} diff --git a/internal/tui/styles/functions.go b/internal/tui/styles/functions.go index 422250b..d031438 100644 --- a/internal/tui/styles/functions.go +++ b/internal/tui/styles/functions.go @@ -39,5 +39,5 @@ func hexToRGB(hex string) (int, int, int) { } func ApplyGradientToFooter(text string) string { - return gradientText("REQ", footerNameFGFrom, footerNameFGTo, footerNameStyle, footerNameBGStyle) + return gradientText("REQ", AppTheme.FooterNameFGFrom, AppTheme.FooterNameFGTo, footerNameStyle, footerNameBGStyle) } diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index d712c2d..7c6c7af 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -6,8 +6,8 @@ func RequestLayout(height, width int) func(...string) string { return lipgloss.NewStyle(). Height(height). Width(width). - Margin(0, 0, 0, 5). - Padding(1, 0, 0, 10). + Margin(0, 0, 0, 1). + Padding(1, 0, 0, 0). Render } @@ -15,7 +15,7 @@ func UrlInputStyle(active bool) lipgloss.Style { var color lipgloss.Color if active { - color = accent + color = AppTheme.Accent } return lipgloss.NewStyle(). @@ -30,6 +30,11 @@ func ResponseStyle(height, width int) func(...string) string { } var ( - ActiveRequestItem = lipgloss.NewStyle().Foreground(accent) + ActiveRequestItem = lipgloss.NewStyle().Foreground(AppTheme.Accent) InactiveRequestItem = lipgloss.NewStyle() ) + +func initRequestStyles() { + ActiveRequestItem = lipgloss.NewStyle().Foreground(AppTheme.Accent) + InactiveRequestItem = lipgloss.NewStyle() +} diff --git a/internal/tui/styles/theme.go b/internal/tui/styles/theme.go new file mode 100644 index 0000000..649242a --- /dev/null +++ b/internal/tui/styles/theme.go @@ -0,0 +1,33 @@ +package styles + +import ( + "github.com/charmbracelet/lipgloss" +) + +type Theme struct { + FooterNameBG lipgloss.Color + FooterNameFGFrom lipgloss.Color + FooterNameFGTo lipgloss.Color + Accent lipgloss.Color + HeadingForeground lipgloss.Color + FooterSegmentBG lipgloss.Color + FooterSegmentFG lipgloss.Color + HelpFG lipgloss.Color +} + +var ( + AppTheme Theme +) + +func init() { + SetTheme(DefaultTheme) +} + +func SetTheme(newTheme Theme) { + AppTheme = newTheme + + // reset styles + initRequestStyles() + initCollectionStyles() + initAppStyles() +} diff --git a/internal/tui/views/collections-view-helper.go b/internal/tui/views/collections-view-helper.go index 75bd68d..cce4df6 100644 --- a/internal/tui/views/collections-view-helper.go +++ b/internal/tui/views/collections-view-helper.go @@ -15,7 +15,7 @@ func createCollectionDelegate(active bool) list.ItemDelegate { d.Styles.SelectedTitle = styles.SelectedInactiveListStyle } - d.Styles.SelectedDesc = styles.UnselectedListStyle + d.Styles.SelectedDesc = styles.SelectedActiveListStyle return d } diff --git a/internal/tui/views/collections-view.go b/internal/tui/views/collections-view.go index 5224c6f..50e6a00 100644 --- a/internal/tui/views/collections-view.go +++ b/internal/tui/views/collections-view.go @@ -27,23 +27,23 @@ type CollectionsView struct { order int } -func (c CollectionsView) Init() tea.Cmd { +func (c *CollectionsView) Init() tea.Cmd { return nil } -func (c CollectionsView) Name() string { +func (c *CollectionsView) Name() string { return "Collections" } -func (c CollectionsView) Help() []key.Binding { +func (c *CollectionsView) Help() []key.Binding { return c.list.Help() } -func (c CollectionsView) GetFooterSegment() string { +func (c *CollectionsView) GetFooterSegment() string { return c.list.GetSelected().Title() } -func (c CollectionsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { +func (c *CollectionsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { var cmd tea.Cmd var cmds []tea.Cmd switch msg := msg.(type) { @@ -70,6 +70,15 @@ func (c CollectionsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case messages.DeleteItem: c.manager.Delete(context.Background(), msg.ItemID) c.list.RefreshItems() + case messages.ChooseItem[optionsProvider.Option]: + c.list.OnBlur() + return c, func() tea.Msg { + return messages.NavigateToView{ + ViewName: string(Endpoints), + Target: MainModel, + Data: msg.Item, + } + } } c.list, cmd = c.list.Update(msg) @@ -78,23 +87,23 @@ func (c CollectionsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return c, tea.Batch(cmds...) } -func (c CollectionsView) View() string { +func (c *CollectionsView) View() string { return c.list.View() } -func (c CollectionsView) SetState(items ...any) error { +func (c *CollectionsView) SetState(items ...any) error { return errors.New("This view does not implement set state") } -func (c CollectionsView) Order() int { +func (c *CollectionsView) Order() int { return c.order } -func (c CollectionsView) OnFocus() { - +func (c *CollectionsView) OnFocus() tea.Cmd { + return c.list.UpdateState() } -func (c CollectionsView) OnBlur() { +func (c *CollectionsView) OnBlur() { } diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go index d44d704..9c8a5ac 100644 --- a/internal/tui/views/endpoints-view-helper.go +++ b/internal/tui/views/endpoints-view-helper.go @@ -13,7 +13,7 @@ func createEndpointsDelegate(active bool) list.ItemDelegate { d.Styles.SelectedTitle = styles.SelectedInactiveListStyle } - d.Styles.SelectedDesc = styles.UnselectedListStyle + d.Styles.SelectedDesc = styles.SelectedActiveListStyle return d } diff --git a/internal/tui/views/endpoints-view.go b/internal/tui/views/endpoints-view.go index 3171bf6..a747310 100644 --- a/internal/tui/views/endpoints-view.go +++ b/internal/tui/views/endpoints-view.go @@ -8,20 +8,11 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" -<<<<<<< HEAD - "github.com/charmbracelet/lipgloss" "github.com/yashranjan1/relay/internal/backend/database" "github.com/yashranjan1/relay/internal/backend/endpoints" optionsProvider "github.com/yashranjan1/relay/internal/tui/components/OptionsProvider" "github.com/yashranjan1/relay/internal/tui/keybinds" "github.com/yashranjan1/relay/internal/tui/messages" -======= - "github.com/maniac-en/req/internal/backend/database" - "github.com/maniac-en/req/internal/backend/endpoints" - optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" - "github.com/maniac-en/req/internal/tui/keybinds" - "github.com/maniac-en/req/internal/tui/messages" ->>>>>>> 2463b9c (refactor: Improve TUI architecture and state handling) ) type epFocused string @@ -87,7 +78,10 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return messages.NavigateToView{ ViewName: "request", Target: MainModel, - Data: msg.Item.ID, + Data: EndpointData{ + Collection: e.collection, + EndpointID: msg.Item.ID, + }, } } case messages.NavigateToView: @@ -97,7 +91,6 @@ func (e *EndpointsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { // FIX: idek // e.requestView.OnBlur() e.focused = listView - e.list.OnFocus() case tea.KeyMsg: switch { @@ -128,8 +121,8 @@ func (e *EndpointsView) View() string { return e.list.View() } -func (e *EndpointsView) OnFocus() { - +func (e *EndpointsView) OnFocus() tea.Cmd { + return e.list.UpdateState() } func (e *EndpointsView) SetState(items ...any) error { diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index c7dcf6d..d71b3cb 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -15,6 +15,7 @@ import ( "github.com/maniac-en/req/internal/backend/http" componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" + optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" viewport "github.com/maniac-en/req/internal/tui/components/ViewPort" "github.com/maniac-en/req/internal/tui/keybinds" @@ -51,6 +52,7 @@ type RequestView struct { order int update func(context.Context, int64, endpoints.EndpointData) (endpoints.EndpointEntity, error) endpoint endpoints.EndpointEntity + collection optionsProvider.Option } func (r *RequestView) Init() tea.Cmd { @@ -80,7 +82,7 @@ func (r *RequestView) Help() []key.Binding { } func (r *RequestView) GetFooterSegment() string { - return "" + return fmt.Sprintf("%s/%s", r.collection.Name, r.endpoint.Name) } func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { @@ -112,7 +114,7 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { return r, func() tea.Msg { return messages.NavigateToView{ ViewName: Endpoints, - Target: Endpoints, + Target: MainModel, } } case key.Matches(msg, keybinds.Keys.Next): @@ -179,7 +181,7 @@ func (r *RequestView) shift(next bool) { if next { r.index = (r.index + 1) % len(componentList) } else { - r.index = (r.index - 1) % len(componentList) + r.index = (r.index - 1 + len(componentList)) % len(componentList) } r.endpoint = r.components[r.focused].UpdateState(r.endpoint) r.components[r.focused].OnBlur() @@ -221,8 +223,9 @@ func (r *RequestView) View() string { func (r *RequestView) SetState(items ...any) error { if len(items) == 1 { - if id, ok := items[0].(int64); ok { - ep, err := r.epManager.Read(context.Background(), id) + if data, ok := items[0].(EndpointData); ok { + ep, err := r.epManager.Read(context.Background(), data.EndpointID) + r.collection = data.Collection if err != nil { // FIX: do something over here } @@ -240,8 +243,9 @@ func (r *RequestView) Order() int { return r.order } -func (r *RequestView) OnFocus() { +func (r *RequestView) OnFocus() tea.Cmd { r.components[r.focused].OnFocus() + return nil } func (r *RequestView) OnBlur() { diff --git a/internal/tui/views/types.go b/internal/tui/views/types.go index 465b14d..22a30ff 100644 --- a/internal/tui/views/types.go +++ b/internal/tui/views/types.go @@ -3,6 +3,7 @@ package views import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" + optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" ) type ViewInterface interface { @@ -14,6 +15,11 @@ type ViewInterface interface { View() string Order() int SetState(...any) error - OnFocus() + OnFocus() tea.Cmd OnBlur() } + +type EndpointData struct { + EndpointID int64 + Collection optionsProvider.Option +} diff --git a/main.go b/main.go index 0770a8e..78ef8be 100644 --- a/main.go +++ b/main.go @@ -145,7 +145,7 @@ func run() error { // run database migrations if err := runMigrations(); err != nil { - return fmt.Errorf("failed to run migrations", "error", err) + return fmt.Errorf("failed to run migrations: %s", err) } // create database client and managers @@ -171,8 +171,12 @@ func run() error { log.Info("application started successfully") // Entry point for UI + err := config.LoadConfig() + if err != nil { + return fmt.Errorf("fatal error: %w", err) + } program := tea.NewProgram(app.NewAppModel(appContext), tea.WithAltScreen()) - if _, err := program.Run(); err != nil { + if _, err = program.Run(); err != nil { return fmt.Errorf("fatal error: %w", err) } return nil From 9ef5d62217d86b360ddf545222ef513600f8609a Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Sat, 29 Aug 2026 22:16:25 +0200 Subject: [PATCH 38/46] refactor: Update TUI styling and state handling - Adjusted padding and margin settings in style definitions for improved layout consistency. - Updated state setting logic in RequestView to correctly use the viewports state management. --- internal/tui/styles/request-styles.go | 5 ++--- internal/tui/views/request-view.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/tui/styles/request-styles.go b/internal/tui/styles/request-styles.go index 7c6c7af..c668d2c 100644 --- a/internal/tui/styles/request-styles.go +++ b/internal/tui/styles/request-styles.go @@ -6,8 +6,7 @@ func RequestLayout(height, width int) func(...string) string { return lipgloss.NewStyle(). Height(height). Width(width). - Margin(0, 0, 0, 1). - Padding(1, 0, 0, 0). + Padding(1, 0, 0, 1). Render } @@ -26,7 +25,7 @@ func UrlInputStyle(active bool) lipgloss.Style { } func ResponseStyle(height, width int) func(...string) string { - return lipgloss.NewStyle().Margin(0, 0, 0, 5).Padding(1, 0, 0, 5).Height(height).Width(width).Render + return lipgloss.NewStyle().Padding(1, 0, 0, 1).Height(height).Width(width).Render } var ( diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index d71b3cb..b1468b1 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -102,7 +102,7 @@ func (r *RequestView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { if msg.Err != nil { // TODO: do something here idek } - r.SetState(msg.Data) + r.viewport.SetState(msg.Data) r.loading = false case tea.KeyMsg: switch { From 362ec00d2aeb90d9b6348175473b3c80fe331f69 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Thu, 3 Sep 2026 19:38:59 +0200 Subject: [PATCH 39/46] refactor: rebrand project imports and documentation - Update all Go imports and paths from req to relay - Add VitePress documentation site and startup demo assets - Adjust configuration directories and environment variables for Relay --- docs/.vitepress/config.mts | 32 + docs/customization/theme.md | 28 + docs/gifs/startup.gif | Bin 0 -> 310206 bytes docs/gifs/tapes/startup.tape | 66 + docs/index.md | 22 + docs/package.json | 10 + docs/pnpm-lock.yaml | 1197 +++++++++++++++++ docs/quick-start/first-steps.md | 26 + docs/quick-start/installation.md | 29 + internal/config/config.go | 6 +- .../tui/components/ComponentTypes/types.go | 2 +- .../tui/components/MethodPicker/component.go | 6 +- .../tui/components/MethodPicker/config.go | 2 +- internal/tui/components/UrlInput/component.go | 4 +- internal/tui/components/ViewPort/component.go | 4 +- internal/tui/messages/messages.go | 2 +- internal/tui/views/collections-view.go | 7 +- internal/tui/views/endpoints-view-helper.go | 2 +- internal/tui/views/request-view-helper.go | 8 +- internal/tui/views/request-view.go | 20 +- internal/tui/views/types.go | 2 +- main.go | 9 +- 22 files changed, 1445 insertions(+), 39 deletions(-) create mode 100644 docs/.vitepress/config.mts create mode 100644 docs/customization/theme.md create mode 100644 docs/gifs/startup.gif create mode 100644 docs/gifs/tapes/startup.tape create mode 100644 docs/index.md create mode 100644 docs/package.json create mode 100644 docs/pnpm-lock.yaml create mode 100644 docs/quick-start/first-steps.md create mode 100644 docs/quick-start/installation.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 0000000..0748213 --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,32 @@ +import { defineConfig } from 'vitepress' + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: "Relay", + description: "docs for relay", + themeConfig: { + nav: [ + { text: 'Home', link: '/' }, + ], + + sidebar: [ + { + text: 'Quick Start', + items: [ + { text: 'Installation', link: '/quick-start/installation' }, + { text: 'First Steps', link: '/quick-start/first-steps' }, + ] + }, + { + text: 'Make Relay your own', + items: [ + { text: 'Theme', link: '/customization/theme' }, + ] + } + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/yashranjan1/relay' } + ] + } +}) diff --git a/docs/customization/theme.md b/docs/customization/theme.md new file mode 100644 index 0000000..7be671c --- /dev/null +++ b/docs/customization/theme.md @@ -0,0 +1,28 @@ +# Theme Configuration + +Relay allows you to customize its visual appearance by updating your configuration file. + +## Changing the Theme + +You can change the active theme color by updating the `name` field under the `[Theme]` section in your configuration file. + +### Configuration Location +The configuration file is formatted in **TOML** and is stored in your system's standard configuration directory, determined by Go's [`os.UserConfigDir()`](https://pkg.go.dev/os#UserConfigDir): + +* **Linux:** `~/.config/` +* **macOS:** `~/Library/Application Support/` +* **Windows:** `%AppData%` + +### Available Themes +Set the `name` variable to one of the following supported options: + +* `"default"` — Default theme +* `"cyberpunk"` — Cyberpunk theme +* `"light-paper"` — Light Paper theme +* `"forest"` — Forest theme + +### Example (`config.toml`) + +```toml +[Theme] +name = "forest" diff --git a/docs/gifs/startup.gif b/docs/gifs/startup.gif new file mode 100644 index 0000000000000000000000000000000000000000..1eb8549feca4807e1ade490d62fe737213a7c122 GIT binary patch literal 310206 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 literal 0 HcmV?d00001 diff --git a/docs/gifs/tapes/startup.tape b/docs/gifs/tapes/startup.tape new file mode 100644 index 0000000..1635315 --- /dev/null +++ b/docs/gifs/tapes/startup.tape @@ -0,0 +1,66 @@ +# Where should we write the GIF? +Output ../startup.gif + +# Set up a 1200x600 terminal with 46px font. +Set FontSize 18 +Set Width 1920 +Set Height 1080 + +Type "relay" + +Enter + +Sleep 100ms + +Type "a" + +Sleep 200ms + +Type "New Collection" + +Enter + +Sleep 200ms + +Enter + +Sleep 200ms + +Type "a" + +Type "New Endpoint" + +Enter + +Sleep 200ms + +Enter + +Sleep 200ms + +Ctrl+l + +Type "https://jsonplaceholder.typicode.com/users" + +Ctrl+S + +Ctrl+r + +Sleep 500ms + +Ctrl+d + +Sleep 50ms + +Ctrl+d + +Sleep 50ms + +Ctrl+d + +Sleep 50ms + +Enter + +Sleep 2 + diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..b0bb089 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,22 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: "Relay" + text: "Ditch the Electron bloat with style" + tagline: Terminal-native API client built for developers who live in the CLI + actions: + - theme: brand + text: Quick start + link: /quick-start/installation + +features: + - title: Vim-like keybinds + details: Keep your hands on the keyboard. Relay is an extension of your editor, not another tool you have to tab into. + - title: Zero Lag + details: Built natively in Go with zero Electron bloat. + - title: Built in the Open + details: No hidden paywalls, no enterprise-tier lockouts, and no telemetry tracking your requests. +--- + diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..38cbd3a --- /dev/null +++ b/docs/package.json @@ -0,0 +1,10 @@ +{ + "devDependencies": { + "vitepress": "2.0.0-alpha.19" + }, + "scripts": { + "docs:dev": "vitepress dev", + "docs:build": "vitepress build", + "docs:preview": "vitepress preview" + } +} \ No newline at end of file diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml new file mode 100644 index 0000000..b272ed3 --- /dev/null +++ b/docs/pnpm-lock.yaml @@ -0,0 +1,1197 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + vitepress: + specifier: 2.0.0-alpha.19 + version: 2.0.0-alpha.19(postcss@8.5.26) + +packages: + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@docsearch/css@4.7.0': + resolution: {integrity: sha512-Sk5xkdRFeE7PeWjG9l4AfTwdvMfr9wHiwNNCpHXT4v4SNyNMKdHGvEILc31BgaVFGDDNbv5u/a73tofRiwbEZw==} + + '@docsearch/js@4.7.0': + resolution: {integrity: sha512-x5lCqu1tetgsJFkjQ6VSocbHldsRkGEgwg5N98Vx21sq/V5wcmj4u226PY9k+TEpIgQ772zlYbPLTPicWyGnpA==} + + '@docsearch/sidepanel-js@4.7.0': + resolution: {integrity: sha512-A8r34jCU8kcIk2viECEn2msA28ojUF1BLi/3v5OWWc5G2N3jOuuumBXoeYjfr8dA0UxgFSy5R2bt12dnFJQSyA==} + + '@iconify-json/simple-icons@1.2.94': + resolution: {integrity: sha512-l8UWzVxKaqZd9ABsE/M/9p6NyGkQnmCnOoZyhQmjlXCtY5PuL2rcWxOFk2l9pk7ux3ERMPkTLE4jl6kQpTkwxA==} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@oxc-project/types@0.147.0': + resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==} + + '@rolldown/binding-android-arm-eabi@1.2.6': + resolution: {integrity: sha512-b+jTcARdTiFLI6jB4a5XjTm0RWd6KcRfQj/I2356fxUZemiho9zQLxo0RtCuMDAyKcLo6cEltkgbQp6d1+sjjQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.6': + resolution: {integrity: sha512-lkWU8ZJaRk9q3CIEY1Tc7vIFALp3Xw5NfGJo2hQg5oIqNgxWi1zI+IiDEK3r70BF5Dzol1tcXsnzsRc8NLhG+Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.6': + resolution: {integrity: sha512-dgR56NYnvAszm7Ob1B2/Vn0e8bUQYZH2UjVaMMtMVOCKFSfjhfLmuA/9+O+F+ajUdG6B/bSssrKW6JJYASa8jA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.6': + resolution: {integrity: sha512-vpVxFvUCFioJqug7OTvqptkc4yb8UX0AwfDmJpaR/0sWz+BUmqSVAf7c8JkUgnN8YLspb4a/N6NhTyMAmdyQ7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.6': + resolution: {integrity: sha512-h1wG6Y6K3JlRswxsI64qQJqBAy4vrLuHgRbc8CZMGSWTOFRY6ghMApM1NKzB2I0n5xV1fjkE18SuVl2QpLeNpA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.6': + resolution: {integrity: sha512-tbCiqub0q2MVWJKgF5PoAlNWCtQydiOYSLIkd8sByqK/6MMYLJRcSXSYodqYtd0O+Fw7QaVmKKlS4oL94YRZ0w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.6': + resolution: {integrity: sha512-oxK9+baEBPhZG5HB4URY+uU04zJWeZlH6Tb9rB5DK4DF9XR1uXNLXt5Q5ZsugTKayNCNLhkcwz/ye74hRI98dg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.2.6': + resolution: {integrity: sha512-muWCk27FVBEZtv0MsK8gnfSmgczA8KQ0uRVJbTABKhkRfQc38aUrcb7fhi3BNiyseFmgcRsoMfQsSNJ+DbZdSw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.2.6': + resolution: {integrity: sha512-eWDoSfU7Co2qj3vgB3Dt4lj1mG6CoWbcJQkRMP3XJplyCMtuaq3LHvPFjS9QIPvMGWVadJC04Xiy0IdcVPtnwQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.2.6': + resolution: {integrity: sha512-2bWNjRSIayvupRKxXUY2tWG9fYdoUlTqWywHRvE8Eq3GvuQ+f2HeIkve697fIt+IQs/PV8yFsdWuhp1aJ1PdnA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.2.6': + resolution: {integrity: sha512-KekI0gS0wLxe1UBSQSjenBVwou/JkcQPDzBPICGZjxUv9k3RteHDPBQaiOicZUFKRIH2wKEimGwVpnJsbPzu7w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.2.6': + resolution: {integrity: sha512-TvtPnfVr+HtyGiDmPK4VWmlNm7QhNNAcK5Q9A7aOXsI8545yCyaoMaicXrFZ72JzeYjaUVk7yT243zT0jzjFKQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.2.6': + resolution: {integrity: sha512-iOo0VEay2XFhaCcH0sps5XIimkSuOnNaZrf6+ZkoSOQBJPKNU48RkmJv0/lSpipexu5P+ouFgafe5IGr/DiQfg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.6': + resolution: {integrity: sha512-y5NTmmasMS455JlOCO4ZM9krIchv3Mvm1crL1iUPGOPgEzSkves9n0SdC5Sjz6+qWDFhd8/JpfWMH8NSWNHe+A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.6': + resolution: {integrity: sha512-np8iZSLfXlAD4kWhiyq/u0Yt8oZDtRQ8lGhQaCXo2rl37KNjeU0GjJuwr4P3oeZ++ROfofsKNBqR5LTO8aXyWQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@shikijs/core@4.4.3': + resolution: {integrity: sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==} + engines: {node: '>=20'} + + '@shikijs/engine-javascript@4.4.3': + resolution: {integrity: sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==} + engines: {node: '>=20'} + + '@shikijs/engine-oniguruma@4.4.3': + resolution: {integrity: sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==} + engines: {node: '>=20'} + + '@shikijs/langs@4.4.3': + resolution: {integrity: sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==} + engines: {node: '>=20'} + + '@shikijs/primitive@4.4.3': + resolution: {integrity: sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==} + engines: {node: '>=20'} + + '@shikijs/themes@4.4.3': + resolution: {integrity: sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==} + engines: {node: '>=20'} + + '@shikijs/transformers@4.4.3': + resolution: {integrity: sha512-oJSARV6NaWd+rnNJbtnpAdj3Zg0ZVyzsnMgb3vi3HA+35y8lBWUCpOnWsmyiXZIikY+x1BDqrQUgmxfzWh7Jvw==} + engines: {node: '>=20'} + + '@shikijs/types@4.4.3': + resolution: {integrity: sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==} + engines: {node: '>=20'} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + + '@types/linkify-it@5.0.0': + resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==} + + '@types/markdown-it@14.2.0': + resolution: {integrity: sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdurl@2.0.0': + resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@ungap/structured-clone@1.4.0': + resolution: {integrity: sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==} + + '@vitejs/plugin-vue@6.0.8': + resolution: {integrity: sha512-0ZjgOg7oO6farnNGup7yvoM/YXZV84OZxHAwtflItNa/6zzQyVb5LNxyea3FEKEX2XlagIKzrlH7wwxkKgtiew==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@vue/compiler-core@3.5.42': + resolution: {integrity: sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==} + + '@vue/compiler-dom@3.5.42': + resolution: {integrity: sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==} + + '@vue/compiler-sfc@3.5.42': + resolution: {integrity: sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==} + + '@vue/compiler-ssr@3.5.42': + resolution: {integrity: sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==} + + '@vue/devtools-api@8.2.1': + resolution: {integrity: sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==} + + '@vue/devtools-kit@8.2.1': + resolution: {integrity: sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==} + + '@vue/devtools-shared@8.2.1': + resolution: {integrity: sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==} + + '@vue/reactivity@3.5.42': + resolution: {integrity: sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==} + + '@vue/runtime-core@3.5.42': + resolution: {integrity: sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==} + + '@vue/runtime-dom@3.5.42': + resolution: {integrity: sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==} + + '@vue/server-renderer@3.5.42': + resolution: {integrity: sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==} + + '@vue/shared@3.5.42': + resolution: {integrity: sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==} + + '@vueuse/core@14.4.0': + resolution: {integrity: sha512-X4WHz1HlCzCBoYXesUkifzzWBAcZgXG8Fi5iNPQg/epdzOB3gu8Fawj3hvuwYR1nGcXGnvxwYYcUC/71++svtQ==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/integrations@14.4.0': + resolution: {integrity: sha512-oJz9qTgczvA7L1nXQFRU7h8tQbOCoiceqvMMhT9XYMyOGTqLJ2rEa09PON+nD2t48sZUfeOmg4eaWJXV4sZb/w==} + peerDependencies: + async-validator: ^4 + axios: ^1 + change-case: ^5 + drauu: ^0.4 + focus-trap: ^7 || ^8 + fuse.js: ^7 + idb-keyval: ^6 + jwt-decode: ^4 + nprogress: ^0.2 + qrcode: ^1.5 + sortablejs: ^1 + universal-cookie: ^7 || ^8 + vue: ^3.5.0 + peerDependenciesMeta: + async-validator: + optional: true + axios: + optional: true + change-case: + optional: true + drauu: + optional: true + focus-trap: + optional: true + fuse.js: + optional: true + idb-keyval: + optional: true + jwt-decode: + optional: true + nprogress: + optional: true + qrcode: + optional: true + sortablejs: + optional: true + universal-cookie: + optional: true + + '@vueuse/metadata@14.4.0': + resolution: {integrity: sha512-swx/255R6JyHZFJhx845iz5CRWDZdCfvkZOpACWc5+c5WHcG24mv8gUT1WIdFQaHt6dq79rvILd9QnCWiyVm9g==} + + '@vueuse/shared@14.4.0': + resolution: {integrity: sha512-JRgY90Sz8DDtPMsaDflvPMp9xYk69JZAmbuDvAquUVXKr2gEjqtzGNTTthLfckH0BzBqvnu31gb4a8TGLRe79g==} + peerDependencies: + vue: ^3.5.0 + + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + focus-trap@8.2.2: + resolution: {integrity: sha512-qV0g8hRYBqgACcFOH3f9wXc4zPKhr/0z9RI2a6ZijZ72EeBi4g8oBy8zAWuUR1TsMpOzwpUMFvjdasrC41Joug==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mark.js@8.11.1: + resolution: {integrity: sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + minisearch@7.2.0: + resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + oniguruma-parser@0.12.2: + resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} + + oniguruma-to-es@4.3.6: + resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + postcss@8.5.26: + resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} + engines: {node: ^10 || ^12 || >=14} + + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rolldown@1.2.6: + resolution: {integrity: sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + shiki@4.4.3: + resolution: {integrity: sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==} + engines: {node: '>=20'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + tabbable@6.5.0: + resolution: {integrity: sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@8.2.2: + resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 || ^0.5.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitepress@2.0.0-alpha.19: + resolution: {integrity: sha512-WnBsb0Bwr43kXKyiis+lld/7ri3hnMbthS8N3hpFtjjwsdLO4IRmiAE08D7aud4q6oMDf9uwRowxzNqRFe/amw==} + hasBin: true + peerDependencies: + markdown-it-mathjax3: ^4 + postcss: ^8 + peerDependenciesMeta: + markdown-it-mathjax3: + optional: true + postcss: + optional: true + + vue@3.5.42: + resolution: {integrity: sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@docsearch/css@4.7.0': {} + + '@docsearch/js@4.7.0': {} + + '@docsearch/sidepanel-js@4.7.0': {} + + '@iconify-json/simple-icons@1.2.94': + dependencies: + '@iconify/types': 2.0.0 + + '@iconify/types@2.0.0': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@oxc-project/types@0.147.0': {} + + '@rolldown/binding-android-arm-eabi@1.2.6': + optional: true + + '@rolldown/binding-android-arm64@1.2.6': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.6': + optional: true + + '@rolldown/binding-darwin-x64@1.2.6': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.6': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.6': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.6': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.6': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.6': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.6': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.6': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.6': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.6': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.6': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.6': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@shikijs/core@4.4.3': + dependencies: + '@shikijs/primitive': 4.4.3 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.6 + + '@shikijs/engine-oniguruma@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + + '@shikijs/primitive@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/themes@4.4.3': + dependencies: + '@shikijs/types': 4.4.3 + + '@shikijs/transformers@4.4.3': + dependencies: + '@shikijs/core': 4.4.3 + '@shikijs/types': 4.4.3 + + '@shikijs/types@4.4.3': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@types/hast@3.0.5': + dependencies: + '@types/unist': 3.0.3 + + '@types/linkify-it@5.0.0': {} + + '@types/markdown-it@14.2.0': + dependencies: + '@types/linkify-it': 5.0.0 + '@types/mdurl': 2.0.0 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdurl@2.0.0': {} + + '@types/unist@3.0.3': {} + + '@types/web-bluetooth@0.0.21': {} + + '@ungap/structured-clone@1.4.0': {} + + '@vitejs/plugin-vue@6.0.8(vite@8.2.2)(vue@3.5.42)': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.2.2 + vue: 3.5.42 + + '@vue/compiler-core@3.5.42': + dependencies: + '@babel/parser': 7.29.8 + '@vue/shared': 3.5.42 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.42': + dependencies: + '@vue/compiler-core': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/compiler-sfc@3.5.42': + dependencies: + '@babel/parser': 7.29.8 + '@vue/compiler-core': 3.5.42 + '@vue/compiler-dom': 3.5.42 + '@vue/compiler-ssr': 3.5.42 + '@vue/shared': 3.5.42 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.26 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.42': + dependencies: + '@vue/compiler-dom': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/devtools-api@8.2.1': + dependencies: + '@vue/devtools-kit': 8.2.1 + + '@vue/devtools-kit@8.2.1': + dependencies: + '@vue/devtools-shared': 8.2.1 + birpc: 2.9.0 + hookable: 5.5.3 + perfect-debounce: 2.1.0 + + '@vue/devtools-shared@8.2.1': {} + + '@vue/reactivity@3.5.42': + dependencies: + '@vue/shared': 3.5.42 + + '@vue/runtime-core@3.5.42': + dependencies: + '@vue/reactivity': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/runtime-dom@3.5.42': + dependencies: + '@vue/reactivity': 3.5.42 + '@vue/runtime-core': 3.5.42 + '@vue/shared': 3.5.42 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.42': + dependencies: + '@vue/compiler-ssr': 3.5.42 + '@vue/runtime-dom': 3.5.42 + '@vue/shared': 3.5.42 + + '@vue/shared@3.5.42': {} + + '@vueuse/core@14.4.0(vue@3.5.42)': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 14.4.0 + '@vueuse/shared': 14.4.0(vue@3.5.42) + vue: 3.5.42 + + '@vueuse/integrations@14.4.0(focus-trap@8.2.2)(vue@3.5.42)': + dependencies: + '@vueuse/core': 14.4.0(vue@3.5.42) + '@vueuse/shared': 14.4.0(vue@3.5.42) + vue: 3.5.42 + optionalDependencies: + focus-trap: 8.2.2 + + '@vueuse/metadata@14.4.0': {} + + '@vueuse/shared@14.4.0(vue@3.5.42)': + dependencies: + vue: 3.5.42 + + birpc@2.9.0: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + comma-separated-tokens@2.0.3: {} + + csstype@3.2.3: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + entities@7.0.1: {} + + estree-walker@2.0.2: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + focus-trap@8.2.2: + dependencies: + tabbable: 6.5.0 + + fsevents@2.3.3: + optional: true + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hookable@5.5.3: {} + + html-void-elements@3.0.0: {} + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + mark.js@8.11.1: {} + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.4.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + minisearch@7.2.0: {} + + nanoid@3.3.18: {} + + oniguruma-parser@0.12.2: {} + + oniguruma-to-es@4.3.6: + dependencies: + oniguruma-parser: 0.12.2 + regex: 6.1.0 + regex-recursion: 6.0.2 + + perfect-debounce@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + postcss@8.5.26: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + property-information@7.2.0: {} + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rolldown@1.2.6: + dependencies: + '@oxc-project/types': 0.147.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.6 + '@rolldown/binding-android-arm64': 1.2.6 + '@rolldown/binding-darwin-arm64': 1.2.6 + '@rolldown/binding-darwin-x64': 1.2.6 + '@rolldown/binding-freebsd-x64': 1.2.6 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.6 + '@rolldown/binding-linux-arm64-gnu': 1.2.6 + '@rolldown/binding-linux-arm64-musl': 1.2.6 + '@rolldown/binding-linux-ppc64-gnu': 1.2.6 + '@rolldown/binding-linux-s390x-gnu': 1.2.6 + '@rolldown/binding-linux-x64-gnu': 1.2.6 + '@rolldown/binding-linux-x64-musl': 1.2.6 + '@rolldown/binding-openharmony-arm64': 1.2.6 + '@rolldown/binding-win32-arm64-msvc': 1.2.6 + '@rolldown/binding-win32-x64-msvc': 1.2.6 + + shiki@4.4.3: + dependencies: + '@shikijs/core': 4.4.3 + '@shikijs/engine-javascript': 4.4.3 + '@shikijs/engine-oniguruma': 4.4.3 + '@shikijs/langs': 4.4.3 + '@shikijs/themes': 4.4.3 + '@shikijs/types': 4.4.3 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.5 + + source-map-js@1.2.1: {} + + space-separated-tokens@2.0.2: {} + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + tabbable@6.5.0: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + trim-lines@3.0.1: {} + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@8.2.2: + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.26 + rolldown: 1.2.6 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + + vitepress@2.0.0-alpha.19(postcss@8.5.26): + dependencies: + '@docsearch/css': 4.7.0 + '@docsearch/js': 4.7.0 + '@docsearch/sidepanel-js': 4.7.0 + '@iconify-json/simple-icons': 1.2.94 + '@shikijs/core': 4.4.3 + '@shikijs/transformers': 4.4.3 + '@shikijs/types': 4.4.3 + '@types/markdown-it': 14.2.0 + '@vitejs/plugin-vue': 6.0.8(vite@8.2.2)(vue@3.5.42) + '@vue/devtools-api': 8.2.1 + '@vue/shared': 3.5.42 + '@vueuse/core': 14.4.0(vue@3.5.42) + '@vueuse/integrations': 14.4.0(focus-trap@8.2.2)(vue@3.5.42) + focus-trap: 8.2.2 + mark.js: 8.11.1 + minisearch: 7.2.0 + shiki: 4.4.3 + vite: 8.2.2 + vue: 3.5.42 + optionalDependencies: + postcss: 8.5.26 + transitivePeerDependencies: + - '@types/node' + - '@vitejs/devtools' + - async-validator + - axios + - change-case + - drauu + - esbuild + - fuse.js + - idb-keyval + - jiti + - jwt-decode + - less + - nprogress + - qrcode + - sass + - sass-embedded + - sortablejs + - stylus + - sugarss + - terser + - tsx + - typescript + - universal-cookie + - yaml + + vue@3.5.42: + dependencies: + '@vue/compiler-dom': 3.5.42 + '@vue/compiler-sfc': 3.5.42 + '@vue/runtime-dom': 3.5.42 + '@vue/server-renderer': 3.5.42 + '@vue/shared': 3.5.42 + + zwitch@2.0.4: {} diff --git a/docs/quick-start/first-steps.md b/docs/quick-start/first-steps.md new file mode 100644 index 0000000..befbf02 --- /dev/null +++ b/docs/quick-start/first-steps.md @@ -0,0 +1,26 @@ +# First steps + + +Relay helps you manage, edit, and test API endpoints through organized collections. + +![thing](./gifs/startup.gif) + +## Collections +Relay uses **Collections** to group related endpoints together. Each collection serves as a workspace for a specific set of API endpoints. + +## Working with Endpoints + +### 1. Editing a Request +* Select an endpoint from your collection to open the **Request Interface**. +* Customize the request details: + * **HTTP Method:** Change between `GET`, `POST`, `PUT`, `DELETE`, etc. + * **URL:** Update the endpoint path or query parameters. + +### 2. Sending Requests and Viewing Responses +* Click **Send** to execute the request. +* You will automatically be redirected to the **Response Page** to view the results: + * **Status:** Displays the HTTP status code (e.g., `200 OK`, `404 Not Found`). + * **Body:** Shows the returned data payload. + * **Headers:** Displays all returned header key-value pairs. + + diff --git a/docs/quick-start/installation.md b/docs/quick-start/installation.md new file mode 100644 index 0000000..9bacab5 --- /dev/null +++ b/docs/quick-start/installation.md @@ -0,0 +1,29 @@ +--- +outline: deep +--- +# Quick start + +## Prerequisites + +This project needs Go 1.24 >= + + +## Installation +As long as you have the right version of a go, use the following command to install +```sh +go install github.com/yashranjan1/relay@latest +``` + +You can also install a specific version by doing +```sh +# replace the version after the @ with whatever version you are looking for +go install github.com/yashranjan1/relay@v0.1.0-alpha2 +``` + + +## Usage + +Run the app using: +```sh +relay +``` diff --git a/internal/config/config.go b/internal/config/config.go index 4ab7bfa..da0ce23 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -5,9 +5,9 @@ import ( "os" "path" - "github.com/maniac-en/req/internal/log" - "github.com/maniac-en/req/internal/tui/styles" "github.com/pelletier/go-toml/v2" + "github.com/yashranjan1/relay/internal/log" + "github.com/yashranjan1/relay/internal/tui/styles" ) func LoadConfig() error { @@ -17,7 +17,7 @@ func LoadConfig() error { log.Error("Could not load user config dir") return errors.New("Could not load user config dir") } - path := path.Join(configDir, "req", "config.toml") + path := path.Join(configDir, "relay", "config.toml") byteData, err := os.ReadFile(path) if err != nil { diff --git a/internal/tui/components/ComponentTypes/types.go b/internal/tui/components/ComponentTypes/types.go index 2c5c8c9..9bab7d7 100644 --- a/internal/tui/components/ComponentTypes/types.go +++ b/internal/tui/components/ComponentTypes/types.go @@ -3,7 +3,7 @@ package componenttypes import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" - "github.com/maniac-en/req/internal/backend/endpoints" + "github.com/yashranjan1/relay/internal/backend/endpoints" ) type ReqViewComponent interface { diff --git a/internal/tui/components/MethodPicker/component.go b/internal/tui/components/MethodPicker/component.go index d73dc15..4a00722 100644 --- a/internal/tui/components/MethodPicker/component.go +++ b/internal/tui/components/MethodPicker/component.go @@ -4,9 +4,9 @@ import ( "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" - "github.com/maniac-en/req/internal/backend/endpoints" - componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" - "github.com/maniac-en/req/internal/tui/keybinds" + "github.com/yashranjan1/relay/internal/backend/endpoints" + componenttypes "github.com/yashranjan1/relay/internal/tui/components/ComponentTypes" + "github.com/yashranjan1/relay/internal/tui/keybinds" ) type MethodPicker[T any] struct { diff --git a/internal/tui/components/MethodPicker/config.go b/internal/tui/components/MethodPicker/config.go index 2408bc6..096ad74 100644 --- a/internal/tui/components/MethodPicker/config.go +++ b/internal/tui/components/MethodPicker/config.go @@ -3,7 +3,7 @@ package methodpicker import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" - "github.com/maniac-en/req/internal/tui/keybinds" + "github.com/yashranjan1/relay/internal/tui/keybinds" ) type MethodPickerConfig[T any] struct { diff --git a/internal/tui/components/UrlInput/component.go b/internal/tui/components/UrlInput/component.go index f80e40d..55ffb1f 100644 --- a/internal/tui/components/UrlInput/component.go +++ b/internal/tui/components/UrlInput/component.go @@ -5,8 +5,8 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/maniac-en/req/internal/backend/endpoints" - componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" + "github.com/yashranjan1/relay/internal/backend/endpoints" + componenttypes "github.com/yashranjan1/relay/internal/tui/components/ComponentTypes" ) type UrlInput struct { diff --git a/internal/tui/components/ViewPort/component.go b/internal/tui/components/ViewPort/component.go index b30438c..c63180d 100644 --- a/internal/tui/components/ViewPort/component.go +++ b/internal/tui/components/ViewPort/component.go @@ -8,8 +8,8 @@ import ( "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/maniac-en/req/internal/backend/http" - "github.com/maniac-en/req/internal/tui/keybinds" + "github.com/yashranjan1/relay/internal/backend/http" + "github.com/yashranjan1/relay/internal/tui/keybinds" ) type Viewport struct { diff --git a/internal/tui/messages/messages.go b/internal/tui/messages/messages.go index 1077f33..38adaa7 100644 --- a/internal/tui/messages/messages.go +++ b/internal/tui/messages/messages.go @@ -1,6 +1,6 @@ package messages -import "github.com/maniac-en/req/internal/backend/http" +import "github.com/yashranjan1/relay/internal/backend/http" type ItemAdded struct { Item string diff --git a/internal/tui/views/collections-view.go b/internal/tui/views/collections-view.go index 50e6a00..54b78f8 100644 --- a/internal/tui/views/collections-view.go +++ b/internal/tui/views/collections-view.go @@ -55,12 +55,7 @@ func (c *CollectionsView) Update(msg tea.Msg) (ViewInterface, tea.Cmd) { case messages.ItemAdded: _, err := c.manager.Create(context.Background(), msg.Item) if err != nil { - return c, func() tea.Msg { - return messages.ShowError{Message: err.Error()} - } - } - return c, func() tea.Msg { - return messages.RefreshItemsList{} + // TODO: handle this } case messages.RefreshItemsList: c.list.RefreshItems() diff --git a/internal/tui/views/endpoints-view-helper.go b/internal/tui/views/endpoints-view-helper.go index 9c8a5ac..87d8d4e 100644 --- a/internal/tui/views/endpoints-view-helper.go +++ b/internal/tui/views/endpoints-view-helper.go @@ -2,7 +2,7 @@ package views import ( "github.com/charmbracelet/bubbles/list" - "github.com/maniac-en/req/internal/tui/styles" + "github.com/yashranjan1/relay/internal/tui/styles" ) func createEndpointsDelegate(active bool) list.ItemDelegate { diff --git a/internal/tui/views/request-view-helper.go b/internal/tui/views/request-view-helper.go index b935c15..711a279 100644 --- a/internal/tui/views/request-view-helper.go +++ b/internal/tui/views/request-view-helper.go @@ -8,10 +8,10 @@ import ( "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" - urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" - "github.com/maniac-en/req/internal/tui/keybinds" - "github.com/maniac-en/req/internal/tui/styles" + methodpicker "github.com/yashranjan1/relay/internal/tui/components/MethodPicker" + urlinput "github.com/yashranjan1/relay/internal/tui/components/UrlInput" + "github.com/yashranjan1/relay/internal/tui/keybinds" + "github.com/yashranjan1/relay/internal/tui/styles" ) func createRequestDelegate(active bool) list.ItemDelegate { diff --git a/internal/tui/views/request-view.go b/internal/tui/views/request-view.go index b1468b1..944c2bf 100644 --- a/internal/tui/views/request-view.go +++ b/internal/tui/views/request-view.go @@ -11,16 +11,16 @@ import ( "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/maniac-en/req/internal/backend/endpoints" - "github.com/maniac-en/req/internal/backend/http" - componenttypes "github.com/maniac-en/req/internal/tui/components/ComponentTypes" - methodpicker "github.com/maniac-en/req/internal/tui/components/MethodPicker" - optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" - urlinput "github.com/maniac-en/req/internal/tui/components/UrlInput" - viewport "github.com/maniac-en/req/internal/tui/components/ViewPort" - "github.com/maniac-en/req/internal/tui/keybinds" - "github.com/maniac-en/req/internal/tui/messages" - "github.com/maniac-en/req/internal/tui/styles" + "github.com/yashranjan1/relay/internal/backend/endpoints" + "github.com/yashranjan1/relay/internal/backend/http" + componenttypes "github.com/yashranjan1/relay/internal/tui/components/ComponentTypes" + methodpicker "github.com/yashranjan1/relay/internal/tui/components/MethodPicker" + optionsProvider "github.com/yashranjan1/relay/internal/tui/components/OptionsProvider" + urlinput "github.com/yashranjan1/relay/internal/tui/components/UrlInput" + viewport "github.com/yashranjan1/relay/internal/tui/components/ViewPort" + "github.com/yashranjan1/relay/internal/tui/keybinds" + "github.com/yashranjan1/relay/internal/tui/messages" + "github.com/yashranjan1/relay/internal/tui/styles" ) type reqFocused string diff --git a/internal/tui/views/types.go b/internal/tui/views/types.go index 22a30ff..e583977 100644 --- a/internal/tui/views/types.go +++ b/internal/tui/views/types.go @@ -3,7 +3,7 @@ package views import ( "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" - optionsProvider "github.com/maniac-en/req/internal/tui/components/OptionsProvider" + optionsProvider "github.com/yashranjan1/relay/internal/tui/components/OptionsProvider" ) type ViewInterface interface { diff --git a/main.go b/main.go index 78ef8be..399cba5 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ import ( "github.com/yashranjan1/relay/internal/backend/endpoints" "github.com/yashranjan1/relay/internal/backend/history" "github.com/yashranjan1/relay/internal/backend/http" + "github.com/yashranjan1/relay/internal/config" "github.com/yashranjan1/relay/internal/log" "github.com/yashranjan1/relay/internal/tui/app" ) @@ -63,12 +64,12 @@ func initPaths() error { if err != nil { return fmt.Errorf("error reading user's cache path: %w", err) } - APPDIR = filepath.Join(userCacheDir, "req") + APPDIR = filepath.Join(userCacheDir, "relay") if err := os.MkdirAll(APPDIR, 0o755); err != nil { return fmt.Errorf("error creating app directory: %w", err) } DBPATH = filepath.Join(APPDIR, "app.db") - LOGPATH = filepath.Join(APPDIR, "req.log") + LOGPATH = filepath.Join(APPDIR, "relay.log") return nil } @@ -128,7 +129,7 @@ func run() error { // initialize logging logLevel := slog.LevelInfo - if os.Getenv("REQ_DEBUG") == "1" || os.Getenv("REQ_LOG_LEVEL") == "debug" { + if os.Getenv("RELAY_DEBUG") == "1" || os.Getenv("RELAY_LOG_LEVEL") == "debug" { logLevel = slog.LevelDebug } log.Initialize(log.Config{ @@ -141,7 +142,7 @@ func run() error { } }() - log.Info("starting req application") + log.Info("starting relay application") // run database migrations if err := runMigrations(); err != nil { From 24de68ac2be927fc320bdb5d1ddd56b161d866bb Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Thu, 3 Sep 2026 21:29:16 +0200 Subject: [PATCH 40/46] docs: fix startup gif path in first steps guide - Update relative path for startup gif asset - Correct broken image reference in quick start documentation --- docs/quick-start/first-steps.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/quick-start/first-steps.md b/docs/quick-start/first-steps.md index befbf02..05d57f4 100644 --- a/docs/quick-start/first-steps.md +++ b/docs/quick-start/first-steps.md @@ -3,7 +3,7 @@ Relay helps you manage, edit, and test API endpoints through organized collections. -![thing](./gifs/startup.gif) +![thing](../gifs/startup.gif) ## Collections Relay uses **Collections** to group related endpoints together. Each collection serves as a workspace for a specific set of API endpoints. From 183e3d09ace7c5b76e89478ba611a321268a2c6b Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Thu, 3 Sep 2026 21:43:16 +0200 Subject: [PATCH 41/46] test: trigger CI From 4a29d10942db3277e5cbe6769a6488e086575d57 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Thu, 3 Sep 2026 21:51:59 +0200 Subject: [PATCH 42/46] ci: add workflow_dispatch trigger to CI/CD workflow Lets the build/test/format-check workflow be run manually, as a fallback since push/pull_request triggers aren't firing on this repo. --- .github/workflows/ci-cd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 288a375..e37d537 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -9,6 +9,7 @@ on: branches: - main - 'v[0-9]+.[0-9]+.[0-9]+' + workflow_dispatch: jobs: build-test: From d482846a3b5334c730a7e0aa32f12d267879122f Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Thu, 3 Sep 2026 21:58:28 +0200 Subject: [PATCH 43/46] test: verify actions enabled From 7db38ed636510d5cdfb535b643078620980229ab Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Thu, 3 Sep 2026 22:00:00 +0200 Subject: [PATCH 44/46] fix: gofmt import ordering in testutils/database.go The relay rebrand's import-path rewrite left this import out of gofmt's sort order; never caught until CI actually ran. --- internal/backend/testutils/database.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/backend/testutils/database.go b/internal/backend/testutils/database.go index c23f28f..b6f0148 100644 --- a/internal/backend/testutils/database.go +++ b/internal/backend/testutils/database.go @@ -6,8 +6,8 @@ import ( "database/sql" "testing" - "github.com/yashranjan1/relay/internal/backend/database" _ "github.com/mattn/go-sqlite3" + "github.com/yashranjan1/relay/internal/backend/database" ) // SetupTestDB creates an in-memory SQLite database with specified tables From 1b35f8195b19ec045af63392cfd12f1852888820 Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Thu, 3 Sep 2026 22:04:09 +0200 Subject: [PATCH 45/46] fix: prevent editing invalid selected items - Add check for invalid item ID before entering edit mode - Prevent out of bounds panics when editing an empty list --- internal/tui/components/OptionsProvider/component.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index 35b225b..651faf0 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -77,6 +77,9 @@ func (o OptionsProvider[T, U]) Update(msg tea.Msg) (OptionsProvider[T, U], tea.C } } case key.Matches(msg, o.keys.EditItem): + if o.GetSelected().ID == -1 { + return o, tea.Batch(cmds...) + } o.list.SetSize(o.list.Width(), o.height-lipgloss.Height(o.input.View())) o.input.SetInput(o.GetSelected().Name) o.input.OnFocus(o.GetSelected().ID) From 951752acc93e761320f43a49096a327f7972fe7c Mon Sep 17 00:00:00 2001 From: yashranjan1 Date: Thu, 3 Sep 2026 22:07:49 +0200 Subject: [PATCH 46/46] fix: prevent deletion of invalid selected items - Add check to ensure selected item ID is not negative before dispatching DeleteItem message - Avoid unintended delete events when no valid item is focused --- internal/tui/components/OptionsProvider/component.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/tui/components/OptionsProvider/component.go b/internal/tui/components/OptionsProvider/component.go index 651faf0..dbb6320 100644 --- a/internal/tui/components/OptionsProvider/component.go +++ b/internal/tui/components/OptionsProvider/component.go @@ -68,6 +68,9 @@ func (o OptionsProvider[T, U]) Update(msg tea.Msg) (OptionsProvider[T, U], tea.C o.focused = textComponent return o, tea.Batch(cmds...) case key.Matches(msg, o.keys.DeleteItem): + if o.GetSelected().ID == -1 { + return o, tea.Batch(cmds...) + } return o, func() tea.Msg { return messages.DeleteItem{ItemID: int64(o.GetSelected().ID)} } case key.Matches(msg, o.keys.Choose): return o, func() tea.Msg {