diff --git a/internal/quickstart/choose_sdk.go b/internal/quickstart/choose_sdk.go index da67a194..68948f45 100644 --- a/internal/quickstart/choose_sdk.go +++ b/internal/quickstart/choose_sdk.go @@ -5,6 +5,7 @@ import ( "io" "strings" + "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" tea "github.com/charmbracelet/bubbletea" @@ -22,6 +23,8 @@ const ( ) type chooseSDKModel struct { + help help.Model + helpKeys keyMap list list.Model selectedIndex int selectedSDK sdkDetail @@ -33,12 +36,26 @@ func NewChooseSDKModel(selectedIndex int) tea.Model { // reset title styles l.Styles.Title = lipgloss.NewStyle() l.Styles.TitleBar = lipgloss.NewStyle() + l.SetShowHelp(false) l.SetShowPagination(true) l.SetShowStatusBar(false) l.SetFilteringEnabled(false) // TODO: try to get filtering working l.Paginator.PerPage = 5 return chooseSDKModel{ + help: help.New(), + helpKeys: keyMap{ + Back: BindingBack, + CursorUp: BindingCursorUp, + CursorDown: BindingCursorDown, + PrevPage: BindingPrevPage, + NextPage: BindingNextPage, + GoToStart: BindingGoToStart, + GoToEnd: BindingGoToEnd, + ShowFullHelp: BindingShowFullHelp, + CloseFullHelp: BindingCloseFullHelp, + Quit: BindingQuit, + }, list: l, selectedIndex: selectedIndex, } @@ -53,13 +70,15 @@ func (m chooseSDKModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch { - case key.Matches(msg, keys.Enter): + case key.Matches(msg, pressableKeys.Enter): i, ok := m.list.SelectedItem().(sdkDetail) if ok { m.selectedSDK = i m.selectedSDK.index = m.list.Index() cmd = sendChoseSDKMsg(m.selectedSDK) } + case key.Matches(msg, m.helpKeys.CloseFullHelp): + m.help.ShowAll = !m.help.ShowAll default: m.list, cmd = m.list.Update(msg) } @@ -71,7 +90,9 @@ func (m chooseSDKModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } func (m chooseSDKModel) View() string { - return m.list.View() + helpView := m.help.View(m.helpKeys) + + return m.list.View() + "\n\n" + helpView } type sdkDetail struct { diff --git a/internal/quickstart/container.go b/internal/quickstart/container.go index e1ee230f..9bd43bc2 100644 --- a/internal/quickstart/container.go +++ b/internal/quickstart/container.go @@ -65,10 +65,10 @@ func (m ContainerModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.width = msg.Width case tea.KeyMsg: switch { - case key.Matches(msg, keys.Quit): + case key.Matches(msg, pressableKeys.Quit): m.quitting = true cmd = tea.Quit - case key.Matches(msg, keys.Back): + case key.Matches(msg, pressableKeys.Back): switch m.currentStep { case stepCreateFlag: // can't go back @@ -172,29 +172,3 @@ func (m ContainerModel) View() string { return wordwrap.String(out, m.width) } - -type keyMap struct { - Back key.Binding - Enter key.Binding - Quit key.Binding - Tab key.Binding -} - -var keys = keyMap{ - Back: key.NewBinding( - key.WithKeys("esc"), - key.WithHelp("esc", "go back"), - ), - Enter: key.NewBinding( - key.WithKeys("enter"), - key.WithHelp("enter", "select"), - ), - Quit: key.NewBinding( - key.WithKeys("ctrl+c"), - key.WithHelp("q", "quit"), - ), - Tab: key.NewBinding( - key.WithKeys("tab"), - key.WithHelp("tab", "toggle"), - ), -} diff --git a/internal/quickstart/create_flag.go b/internal/quickstart/create_flag.go index 96d5f2bb..f5662132 100644 --- a/internal/quickstart/create_flag.go +++ b/internal/quickstart/create_flag.go @@ -2,12 +2,14 @@ package quickstart import ( "fmt" - "ldcli/internal/flags" + "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + + "ldcli/internal/flags" ) const defaultFlagName = "My New Flag" @@ -16,6 +18,8 @@ type createFlagModel struct { accessToken string baseUri string client flags.Client + help help.Model + helpKeys keyMap textInput textinput.Model } @@ -30,7 +34,11 @@ func NewCreateFlagModel(client flags.Client, accessToken, baseUri string) tea.Mo accessToken: accessToken, baseUri: baseUri, client: client, - textInput: ti, + help: help.New(), + helpKeys: keyMap{ + Quit: BindingQuit, + }, + textInput: ti, } } @@ -43,7 +51,7 @@ func (m createFlagModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch { - case key.Matches(msg, keys.Enter): + case key.Matches(msg, pressableKeys.Enter): input := m.textInput.Value() if input == "" { input = defaultFlagName @@ -54,8 +62,6 @@ func (m createFlagModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } return m, sendCreateFlagMsg(m.client, m.accessToken, m.baseUri, input, flagKey, defaultProjKey) - case key.Matches(msg, keys.Quit): - return m, tea.Quit default: m.textInput, cmd = m.textInput.Update(msg) } @@ -67,9 +73,10 @@ func (m createFlagModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m createFlagModel) View() string { style := lipgloss.NewStyle(). MarginLeft(2) + helpView := m.help.View(m.helpKeys) return fmt.Sprintf( "Name your first feature flag (enter for default value):%s", style.Render(m.textInput.View()), - ) + "\n" + ) + "\n\n" + helpView } diff --git a/internal/quickstart/help.go b/internal/quickstart/help.go new file mode 100644 index 00000000..3f846902 --- /dev/null +++ b/internal/quickstart/help.go @@ -0,0 +1,95 @@ +package quickstart + +import "github.com/charmbracelet/bubbles/key" + +var ( + BindingBack = key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "back"), + ) + BindingCursorUp = key.NewBinding( + key.WithKeys("up", "k"), + key.WithHelp("↑/k", "up"), + ) + BindingCursorDown = key.NewBinding( + key.WithKeys("down", "j"), + key.WithHelp("↓/j", "down"), + ) + BindingPrevPage = key.NewBinding( + key.WithKeys("left", "h", "pgup", "b", "u"), + key.WithHelp("←/h/pgup", "prev page"), + ) + BindingNextPage = key.NewBinding( + key.WithKeys("right", "l", "pgdown", "f", "d"), + key.WithHelp("→/l/pgdn", "next page"), + ) + BindingGoToStart = key.NewBinding( + key.WithKeys("home", "g"), + key.WithHelp("g/home", "go to start"), + ) + BindingGoToEnd = key.NewBinding( + key.WithKeys("end", "G"), + key.WithHelp("G/end", "go to end"), + ) + BindingShowFullHelp = key.NewBinding( + key.WithKeys("?"), + key.WithHelp("?", "more"), + ) + BindingCloseFullHelp = key.NewBinding( + key.WithKeys("?"), + key.WithHelp("?", "close help"), + ) + BindingQuit = key.NewBinding( + key.WithKeys("ctrl+c"), + key.WithHelp("ctrl+c", "quit"), + ) +) + +// keyMap defines all the possible key presses we would respond to +type keyMap struct { + Back key.Binding + CloseFullHelp key.Binding + CursorDown key.Binding + CursorUp key.Binding + Enter key.Binding + GoToEnd key.Binding + GoToStart key.Binding + NextPage key.Binding + PrevPage key.Binding + Quit key.Binding + ShowFullHelp key.Binding + Tab key.Binding +} + +func (k keyMap) FullHelp() [][]key.Binding { + return [][]key.Binding{ + {k.CursorUp, k.CursorDown, k.PrevPage, k.NextPage}, + {k.Back, k.Quit, k.CloseFullHelp}, + } +} + +func (k keyMap) ShortHelp() []key.Binding { + return []key.Binding{k.Back, k.Quit, k.ShowFullHelp} +} + +// pressableKeys are the possible key presses we support for all steps. +// We don't necessarily want to show these in the help text, but we want to handle them when +// pressed. +var pressableKeys = keyMap{ + Back: key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "go back"), + ), + Enter: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "select"), + ), + Quit: key.NewBinding( + key.WithKeys("ctrl+c"), + key.WithHelp("ctrl+c", "quit"), + ), + Tab: key.NewBinding( + key.WithKeys("tab"), + key.WithHelp("tab", "toggle"), + ), +} diff --git a/internal/quickstart/show_sdk_instructions.go b/internal/quickstart/show_sdk_instructions.go index 771b47e4..01bae868 100644 --- a/internal/quickstart/show_sdk_instructions.go +++ b/internal/quickstart/show_sdk_instructions.go @@ -3,6 +3,7 @@ package quickstart import ( "fmt" + "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/spinner" tea "github.com/charmbracelet/bubbletea" @@ -19,6 +20,8 @@ type showSDKInstructionsModel struct { canonicalName string displayName string flagKey string + help help.Model + helpKeys keyMap instructions string sdkKey string spinner spinner.Model @@ -42,8 +45,13 @@ func NewShowSDKInstructionsModel( canonicalName: canonicalName, displayName: displayName, flagKey: flagKey, - spinner: s, - url: url, + help: help.New(), + helpKeys: keyMap{ + Back: BindingBack, + Quit: BindingQuit, + }, + spinner: s, + url: url, } } @@ -60,7 +68,7 @@ func (m showSDKInstructionsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch { - case key.Matches(msg, keys.Enter): + case key.Matches(msg, pressableKeys.Enter): // TODO: only if all data are fetched? cmd = sendShowToggleFlagMsg() } @@ -78,6 +86,7 @@ func (m showSDKInstructionsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m showSDKInstructionsModel) View() string { style := lipgloss.NewStyle().Border(lipgloss.NormalBorder(), true, false) + helpView := m.help.View(m.helpKeys) md, err := m.renderMarkdown() if err != nil { return fmt.Sprintf("error rendering instructions: %s", err) @@ -94,7 +103,7 @@ func (m showSDKInstructionsModel) View() string { style.Render(md), ), 0, - ) + ) + "\n\n" + helpView } func (m showSDKInstructionsModel) renderMarkdown() (string, error) { diff --git a/internal/quickstart/toggle_flag.go b/internal/quickstart/toggle_flag.go index 1a44efce..2a9e61ba 100644 --- a/internal/quickstart/toggle_flag.go +++ b/internal/quickstart/toggle_flag.go @@ -2,9 +2,12 @@ package quickstart import ( "fmt" + + "github.com/charmbracelet/bubbles/help" "github.com/charmbracelet/bubbles/key" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "ldcli/internal/flags" ) @@ -15,6 +18,8 @@ type toggleFlagModel struct { enabled bool flagKey string flagWasEnabled bool + help help.Model + helpKeys keyMap sdkKind string } @@ -24,7 +29,12 @@ func NewToggleFlagModel(client flags.Client, accessToken string, baseUri string, baseUri: baseUri, client: client, flagKey: flagKey, - sdkKind: sdkKind, + help: help.New(), + helpKeys: keyMap{ + Back: BindingBack, + Quit: BindingQuit, + }, + sdkKind: sdkKind, } } @@ -37,9 +47,9 @@ func (m toggleFlagModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch { - case key.Matches(msg, keys.Quit): + case key.Matches(msg, pressableKeys.Quit): return m, tea.Quit - case key.Matches(msg, keys.Tab): + case key.Matches(msg, pressableKeys.Tab): m.flagWasEnabled = true m.enabled = !m.enabled return m, sendToggleFlagMsg(m.client, m.accessToken, m.baseUri, m.flagKey, m.enabled) @@ -65,6 +75,7 @@ func (m toggleFlagModel) View() string { margin = 2 toggle = "ON" } + helpView := m.help.View(m.helpKeys) if m.flagWasEnabled { furtherInstructions = fmt.Sprintf("\n\nCheck your %s to see the change!\n\n(press ctrl + c to quit)", logTypeMap[m.sdkKind]) @@ -75,5 +86,5 @@ func (m toggleFlagModel) View() string { Padding(0, 1). MarginRight(margin) - return title + "\n\n" + toggleStyle.Render(toggle) + m.flagKey + furtherInstructions + return title + "\n\n" + toggleStyle.Render(toggle) + m.flagKey + furtherInstructions + "\n\n" + helpView }