feat: even more progress on the UI

This commit is contained in:
2024-11-08 01:01:30 +02:00
parent d8c49197d4
commit fdeaaa454c
2 changed files with 107 additions and 49 deletions

View File

@@ -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)

View File

@@ -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 {
choices = append(choices, m.UnselectedStyle.Render(choice))
choice = m.UnselectedStyle.Render(choice)
}
if i < m.leftCount {
leftChoices = append(leftChoices, choice)
} else {
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) SetChoices(choices ...string) {
assert.Assert(len(choices) != 0, "choices must have at least 1 element")
m.choices = choices
func (m *Model) SetContent(content string) {
m.content = content
}
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() {