diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/client/ui/auth/auth.go | 61 | ||||
| -rw-r--r-- | internal/client/ui/choicepopup/choicepopup.go | 92 |
2 files changed, 143 insertions, 10 deletions
diff --git a/internal/client/ui/auth/auth.go b/internal/client/ui/auth/auth.go index c2a3dd8..f3dc2f6 100644 --- a/internal/client/ui/auth/auth.go +++ b/internal/client/ui/auth/auth.go @@ -10,7 +10,9 @@ import ( tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/kyren223/eko/internal/client/ui" authfield "github.com/kyren223/eko/internal/client/ui/auth/field" + "github.com/kyren223/eko/internal/client/ui/choicepopup" "github.com/kyren223/eko/pkg/assert" ) @@ -64,6 +66,8 @@ type Model struct { fields []authfield.Model signup bool + + popup *choicepopup.Model } func New() Model { @@ -183,11 +187,17 @@ func (m Model) View() string { // NOTE: Without -1 it wraps/truncates, not sure why Padding(0, (vp.Width-width)/2-1, 1) - return lipgloss.Place( + result := lipgloss.Place( m.width, m.height, lipgloss.Center, lipgloss.Center, vp.View(), ) + + if m.popup != nil { + result = ui.PlaceOverlay(0, 0, m.popup.View(), result) + } + + return result } func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -206,36 +216,57 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, m.SetSignup(!m.signup) case tea.KeyCtrlT: - if m.focusIndex == passphraseField || m.focusIndex == passphraseConfirmField { + if m.popup == nil && (m.focusIndex == passphraseField || m.focusIndex == passphraseConfirmField) { m.fields[m.focusIndex].SetRevealed(!m.fields[m.focusIndex].Revealed()) } return m, nil case tea.KeyEnter: + if m.popup != nil { + _, choice := m.popup.Select() + if choice == "sign-up" { + m.popup = nil + return m, m.SetSignup(!m.signup) + } + if choice == "cancel" { + m.popup = nil + return m, nil + } + assert.Never("unexpected choice", "choice", choice) + } + pressedButton := key == tea.KeyEnter && m.focusIndex == len(m.fields) if pressedButton { return m, m.ButtonPressed(msg) } case tea.KeyShiftTab, tea.KeyUp: + if m.popup != nil { + m.popup.ScrollLeft() + return m, nil + } m.CycleBack() return m, m.updateFocus() case tea.KeyDown, tea.KeyTab: + if m.popup != nil { + m.popup.ScrollRight() + return m, nil + } m.CycleForward() return m, m.updateFocus() } } - cmd := func() tea.Cmd { - cmds := make([]tea.Cmd, len((&m).fields)) - for i := range (&m).fields { - (&m).fields[i], cmds[i] = (&m).fields[i].Update(msg) - } - return tea.Batch(cmds...) - }() + if m.popup != nil { + return m, nil + } - return m, cmd + cmds := make([]tea.Cmd, len(m.fields)) + for i := range m.fields { + m.fields[i], cmds[i] = m.fields[i].Update(msg) + } + return m, tea.Batch(cmds...) } func (m *Model) CycleBack() { @@ -332,6 +363,16 @@ func (m *Model) signin() tea.Cmd { // File <file> doesn't exist. // Do you want to go to sign-up? // Sign-up Cancel + popup := choicepopup.New(40, 10) + popup.Dialogue.SetContent(fmt.Sprintf("File '%s' doesn't exist.\nDo you want to sign-up instead?", privateKeyFilepath)) + popup.Dialogue.Style = lipgloss.NewStyle().Border(lipgloss.NormalBorder(), false, false, true) + popup.SetChoices("sign-up", "cancel") + popup.Style = lipgloss.NewStyle().Border(lipgloss.ThickBorder()) + popup.SelectedStyle = lipgloss.NewStyle().Background(focusedStyle.GetForeground()).Padding(0, 1).Margin(0, 1) + popup.UnselectedStyle = lipgloss.NewStyle().Background(grayStyle.GetForeground()).Padding(0, 1).Margin(0, 1) + popup.ChoicesStyle = lipgloss.NewStyle().AlignHorizontal(lipgloss.Right) + popup.Cycle = true + m.popup = &popup return nil } if err != nil { diff --git a/internal/client/ui/choicepopup/choicepopup.go b/internal/client/ui/choicepopup/choicepopup.go new file mode 100644 index 0000000..a41abc9 --- /dev/null +++ b/internal/client/ui/choicepopup/choicepopup.go @@ -0,0 +1,92 @@ +package choicepopup + +import ( + "github.com/charmbracelet/bubbles/viewport" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + + "github.com/kyren223/eko/pkg/assert" +) + +type Model struct { + Width int + Height int + + Dialogue viewport.Model + Cycle bool + + choices []string + index int + + SelectedStyle lipgloss.Style + UnselectedStyle lipgloss.Style + ChoicesStyle lipgloss.Style + Style lipgloss.Style +} + +func New(width, height int) Model { + return Model{ + Width: width, + Height: height, + Dialogue: viewport.New(0, 0), + } +} + +func (m Model) Init() tea.Cmd { + return nil +} + +func (m Model) View() string { + var choices []string + for i, choice := range m.choices { + if i == m.index { + choices = append(choices, m.SelectedStyle.Render(choice)) + } else { + choices = append(choices, m.UnselectedStyle.Render(choice)) + } + } + styledChoices := m.ChoicesStyle.MaxWidth(m.Width).MaxHeight(m.Height). + Render(lipgloss.JoinHorizontal(lipgloss.Center, choices...)) + + m.Dialogue.Width = m.Width + m.Dialogue.Height = m.Height - lipgloss.Height(styledChoices) + + popup := lipgloss.JoinVertical(lipgloss.Left, m.Dialogue.View(), styledChoices) + return m.Style.Render(popup) +} + +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + var cmd tea.Cmd + m.Dialogue, cmd = m.Dialogue.Update(msg) + return m, cmd +} + +func (m *Model) SetChoices(choices ...string) { + assert.Assert(len(choices) != 0, "choices must have at least 1 element") + m.choices = choices + m.index = 0 +} + +func (m *Model) ScrollLeft() { + if m.index == 0 { + if m.Cycle { + m.index = len(m.choices) - 1 + } + return + } + m.index-- +} + +func (m *Model) ScrollRight() { + if m.index == len(m.choices)-1 { + if m.Cycle { + m.index = 0 + } + return + } + m.index++ +} + +func (m Model) Select() (index int, choice string) { + return m.index, m.choices[m.index] +} |
