summaryrefslogtreecommitdiff
path: root/internal/client/ui
diff options
context:
space:
mode:
authorKyren223 <Kyren223@proton.me>2024-11-08 01:01:30 +0200
committerKyren223 <Kyren223@proton.me>2024-11-08 01:01:30 +0200
commitfdeaaa454cb0ff939de3c3247dd97adebdaef96f (patch)
treebd7d428ca121529569121cba670a8055c9a25d1f /internal/client/ui
parentd8c49197d43247972dd58e3b295c1091571b5db1 (diff)
feat: even more progress on the UI
Diffstat (limited to 'internal/client/ui')
-rw-r--r--internal/client/ui/auth/auth.go88
-rw-r--r--internal/client/ui/choicepopup/choicepopup.go68
2 files changed, 107 insertions, 49 deletions
diff --git a/internal/client/ui/auth/auth.go b/internal/client/ui/auth/auth.go
index f3dc2f6..a2faf93 100644
--- a/internal/client/ui/auth/auth.go
+++ b/internal/client/ui/auth/auth.go
@@ -56,6 +56,10 @@ ___] | |__] | \| | | \|
revealIcon = lipgloss.NewStyle().PaddingLeft(1).Render("󰈈 ")
concealIcon = lipgloss.NewStyle().PaddingLeft(1).Render("󰈉 ")
+
+ popupStyle = lipgloss.NewStyle().Border(lipgloss.ThickBorder())
+ choiceSelectedStyle = lipgloss.NewStyle().Background(focusedStyle.GetForeground()).Padding(0, 1).Margin(0, 1)
+ choiceUnselectedStyle = lipgloss.NewStyle().Background(grayStyle.GetForeground()).Padding(0, 1).Margin(0, 1)
)
type Model struct {
@@ -87,6 +91,7 @@ func New() Model {
case usernameField:
field.Header = headerStyle.Render("Username")
field.Input.Placeholder = "Username"
+ field.Input.CharLimit = 48
field.Input.Validate = func(username string) error {
if len(username) == 0 {
return errors.New("Required")
@@ -96,12 +101,7 @@ func New() Model {
case privateKeyField:
field.Header = headerStyle.Render("Private Key")
field.Input.Placeholder = "Path to Private Key"
- // TODO: Consider if I need a custom validate function or not
- // I most likely need it for prompts
- // So if signup is on and private key file exists suggest to either:
- // 1. Overwrite it 2. Switch to sign-in 3. Cancel
- // And if it's off and the file doesn't exist, suggest:
- // 1. Switch to sign-up 2. Cancel
+ field.Input.CharLimit = 100
field.Input.Validate = func(privKey string) error {
if len(privKey) == 0 {
return errors.New("Required")
@@ -194,7 +194,10 @@ func (m Model) View() string {
)
if m.popup != nil {
- result = ui.PlaceOverlay(0, 0, m.popup.View(), result)
+ popup := m.popup.View()
+ x := (m.width - lipgloss.Width(popup)) / 2
+ y := (m.height - lipgloss.Height(popup)) / 2
+ result = ui.PlaceOverlay(x, y, popup, result)
}
return result
@@ -212,9 +215,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyCtrlC:
return m, tea.Quit
- case tea.KeyCtrlS:
- return m, m.SetSignup(!m.signup)
-
case tea.KeyCtrlT:
if m.popup == nil && (m.focusIndex == passphraseField || m.focusIndex == passphraseConfirmField) {
m.fields[m.focusIndex].SetRevealed(!m.fields[m.focusIndex].Revealed())
@@ -226,7 +226,21 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
_, choice := m.popup.Select()
if choice == "sign-up" {
m.popup = nil
- return m, m.SetSignup(!m.signup)
+ return m, m.SetSignup(true)
+ }
+ if choice == "sign-in" {
+ m.popup = nil
+ return m, m.SetSignup(false)
+ }
+ if choice == "overwrite" {
+ // TODO: how should I handle this
+ // I need to somehow use this to notify that the file
+ // should be overwritten, or maybe I should remove this option?
+ // And make sure the user manually deletes/renames/moves the file
+ // So nobody can claim that this deleted their SSH keys
+ // (or more likely: I won't accidentally delete my SSH keys)
+ m.popup = nil
+ return m, nil
}
if choice == "cancel" {
m.popup = nil
@@ -352,27 +366,31 @@ func (m *Model) ButtonPressed(msg tea.Msg) tea.Cmd {
}
func (m *Model) Signup() tea.Cmd {
- return tea.Quit
+ // So if signup is on and private key file exists suggest to either:
+ // 1. Overwrite it 2. Switch to sign-in 3. Cancel
+ privateKeyFilepath := m.fields[privateKeyField].Input.Value()
+ _, err := os.ReadFile(privateKeyFilepath)
+ if errors.Is(err, os.ErrNotExist) {
+ return tea.Quit
+ }
+
+ if err != nil {
+ m.fields[privateKeyField].Input.Err = errors.Unwrap(err)
+ assert.NotNil(errors.Unwrap(err), "there should always be an error to unwrap", "err", err)
+ return nil
+ }
+
+ content := fmt.Sprintf("File '%s' exist.\nDo you want to overwrite or sign-in instead?", privateKeyFilepath)
+ m.popup = createPopup(content, []string{"sign-in", "overwrite"}, []string{"cancel"})
+ return nil
}
func (m *Model) signin() tea.Cmd {
privateKeyFilepath := m.fields[privateKeyField].Input.Value()
_, err := os.ReadFile(privateKeyFilepath)
if errors.Is(err, os.ErrNotExist) {
- // TODO: add suggestion to move to signup, a popup like:
- // 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
+ content := fmt.Sprintf("File '%s' doesn't exist.\nDo you want to sign-up instead?", privateKeyFilepath)
+ m.popup = createPopup(content, []string{"sign-up"}, []string{"cancel"})
return nil
}
if err != nil {
@@ -383,6 +401,24 @@ func (m *Model) signin() tea.Cmd {
return tea.Quit
}
+func createPopup(content string, leftChoices, rightChoices []string) *choicepopup.Model {
+ content = lipgloss.NewStyle().Padding(0, 1).
+ Border(lipgloss.NormalBorder(), false, false, true).
+ Render(content)
+
+ popup := choicepopup.New(lipgloss.Width(content), lipgloss.Height(content)+1)
+
+ popup.SetContent(content)
+ popup.SetChoices(leftChoices, rightChoices)
+ popup.Cycle = true
+
+ popup.Style = popupStyle
+ popup.SelectedStyle = choiceSelectedStyle
+ popup.UnselectedStyle = choiceUnselectedStyle
+
+ return &popup
+}
+
func test() {
// pubKey, privKey, err := ed25519.GenerateKey(nil)
// sshPrivKey, err := ssh.NewSignerFromSigner(privKey)
diff --git a/internal/client/ui/choicepopup/choicepopup.go b/internal/client/ui/choicepopup/choicepopup.go
index a41abc9..b490693 100644
--- a/internal/client/ui/choicepopup/choicepopup.go
+++ b/internal/client/ui/choicepopup/choicepopup.go
@@ -1,7 +1,8 @@
package choicepopup
import (
- "github.com/charmbracelet/bubbles/viewport"
+ "strings"
+
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
@@ -11,12 +12,12 @@ import (
type Model struct {
Width int
Height int
+ Cycle bool
- Dialogue viewport.Model
- Cycle bool
-
- choices []string
- index int
+ content string
+ choices []string
+ index int
+ leftCount int
SelectedStyle lipgloss.Style
UnselectedStyle lipgloss.Style
@@ -26,9 +27,8 @@ type Model struct {
func New(width, height int) Model {
return Model{
- Width: width,
- Height: height,
- Dialogue: viewport.New(0, 0),
+ Width: width,
+ Height: height,
}
}
@@ -37,34 +37,56 @@ func (m Model) Init() tea.Cmd {
}
func (m Model) View() string {
- var choices []string
+ var leftChoices []string
+ var rightChoices []string
for i, choice := range m.choices {
if i == m.index {
- choices = append(choices, m.SelectedStyle.Render(choice))
+ choice = m.SelectedStyle.Render(choice)
+ } else {
+ choice = m.UnselectedStyle.Render(choice)
+ }
+
+ if i < m.leftCount {
+ leftChoices = append(leftChoices, choice)
} else {
- choices = append(choices, m.UnselectedStyle.Render(choice))
+ rightChoices = append(rightChoices, 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)
+ left := lipgloss.JoinHorizontal(lipgloss.Center, leftChoices...)
+ right := lipgloss.JoinHorizontal(lipgloss.Center, rightChoices...)
+ leftWidth := lipgloss.Width(left)
+ rightWidth := lipgloss.Width(right)
- popup := lipgloss.JoinVertical(lipgloss.Left, m.Dialogue.View(), styledChoices)
+ paddingSize := m.Width - leftWidth - rightWidth
+ assert.Assert(paddingSize >= 0, "there should be enough space for all choices", "remaining", paddingSize)
+ padding := strings.Repeat(" ", paddingSize)
+ choices := lipgloss.JoinHorizontal(lipgloss.Center, left, padding, right)
+ styledChoices := m.ChoicesStyle.MaxWidth(m.Width).MaxHeight(m.Height).Render(choices)
+
+ maxContentHeight := m.Height - lipgloss.Height(styledChoices)
+ content := lipgloss.NewStyle().MaxWidth(m.Width).MaxHeight(maxContentHeight).
+ Render(m.content)
+
+ popup := lipgloss.JoinVertical(lipgloss.Left, content, 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
+ return m, nil
+}
+
+func (m *Model) SetContent(content string) {
+ m.content = content
}
-func (m *Model) SetChoices(choices ...string) {
- assert.Assert(len(choices) != 0, "choices must have at least 1 element")
- m.choices = choices
+func (m *Model) SetChoices(leftChoices, rightChoices []string) {
+ assert.Assert(len(leftChoices)+len(rightChoices) != 0, "choices must have at least 1 element")
m.index = 0
+ m.leftCount = len(leftChoices)
+ m.choices = nil
+ m.choices = append(m.choices, leftChoices...)
+ m.choices = append(m.choices, rightChoices...)
}
func (m *Model) ScrollLeft() {