mirror of
https://github.com/Kyren223/eko.git
synced 2026-03-11 02:15:37 +00:00
feat: even more progress on the UI
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user