summaryrefslogtreecommitdiff
path: root/internal/client
diff options
context:
space:
mode:
Diffstat (limited to 'internal/client')
-rw-r--r--internal/client/ui/core/core.go12
-rw-r--r--internal/client/ui/core/networkcreation/networkcreation.go133
-rw-r--r--internal/client/ui/layouts/flex/flex.go19
3 files changed, 137 insertions, 27 deletions
diff --git a/internal/client/ui/core/core.go b/internal/client/ui/core/core.go
index 070ab16..fba3ddb 100644
--- a/internal/client/ui/core/core.go
+++ b/internal/client/ui/core/core.go
@@ -137,7 +137,17 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.networkCreationPopup = &popup
}
- default:
+ case tea.KeyEscape:
+ if m.networkCreationPopup != nil {
+ m.networkCreationPopup = nil
+ }
+
+ case tea.KeyEnter:
+ if m.networkCreationPopup != nil {
+ return m, m.networkCreationPopup.Select()
+ }
+
+ default:
if m.networkCreationPopup != nil {
popup, cmd := m.networkCreationPopup.Update(msg)
m.networkCreationPopup = &popup
diff --git a/internal/client/ui/core/networkcreation/networkcreation.go b/internal/client/ui/core/networkcreation/networkcreation.go
index c911dbb..f21bb73 100644
--- a/internal/client/ui/core/networkcreation/networkcreation.go
+++ b/internal/client/ui/core/networkcreation/networkcreation.go
@@ -1,6 +1,7 @@
package networkcreation
import (
+ "errors"
"strings"
"github.com/charmbracelet/bubbles/textinput"
@@ -8,11 +9,18 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/kyren223/eko/internal/client/ui/colors"
"github.com/kyren223/eko/internal/client/ui/field"
+ "github.com/kyren223/eko/internal/client/ui/layouts/flex"
"github.com/kyren223/eko/pkg/assert"
)
var (
- style = lipgloss.NewStyle().Border(lipgloss.ThickBorder())
+ width = 48
+
+ style = lipgloss.NewStyle().
+ Border(lipgloss.ThickBorder()).
+ Padding(1, 4).
+ Align(lipgloss.Center, lipgloss.Center)
+
headerStyle = lipgloss.NewStyle().Foreground(colors.Turquoise)
fieldBlurredStyle = lipgloss.NewStyle().
@@ -23,11 +31,7 @@ var (
BorderForeground(colors.Focus).
Border(lipgloss.ThickBorder())
- underlineStyle = func(s string, width int, focus bool) string {
- color := colors.Gray
- if focus {
- color = colors.Focus
- }
+ underlineStyle = func(s string, width int, color lipgloss.Color) string {
underline := lipgloss.NewStyle().Foreground(color).
Render(strings.Repeat(lipgloss.ThickBorder().Bottom, width))
return lipgloss.JoinVertical(lipgloss.Left, s, underline)
@@ -35,6 +39,11 @@ var (
iconHeader = headerStyle.Bold(true).Render(" Icon: ")
colorHeader = headerStyle.Bold(true).Italic(true).Render("# ")
+
+ blurredCreate = lipgloss.NewStyle().
+ Background(colors.Gray).Padding(0, 1).Render("Create Network")
+ focusedCreate = lipgloss.NewStyle().
+ Background(colors.Blue).Padding(0, 1).Render("Create Network")
)
const (
@@ -46,49 +55,73 @@ const (
NameField = iota
IconField
ColorField
+ CreateField
FieldCount
)
type Model struct {
- name field.Model
- Style lipgloss.Style
precomputedIconStyle lipgloss.Style
- icon textinput.Model
- color textinput.Model
- Width int
- Height int
- selected int
+ name field.Model
+ icon textinput.Model
+ color textinput.Model
+ create string
+
+ selected int
+ nameWidth int
}
func New() Model {
- name := field.New(30)
+ name := field.New(width)
name.Header = "Network Name"
name.HeaderStyle = headerStyle
name.FocusedStyle = fieldFocusedStyle
name.BlurredStyle = fieldBlurredStyle
- name.Input.CharLimit = 32
+ name.ErrorStyle = lipgloss.NewStyle().Foreground(colors.Error)
+ name.Input.CharLimit = width
name.Focus()
+ name.Input.Validate = func(s string) error {
+ if strings.TrimSpace(s) == "" {
+ return errors.New("cannot be empty")
+ }
+ if strings.TrimSpace(s) != s {
+ return errors.New("no leading/trailing spaces")
+ }
+ return nil
+ }
+
+ nameWidth := lipgloss.Width(name.View())
icon := textinput.New()
icon.Prompt = ""
icon.CharLimit = MaxIconLength
icon.Placeholder = "ic"
+ icon.Validate = func(s string) error {
+ if len(s) == 0 {
+ return errors.New("err")
+ }
+ return nil
+ }
color := textinput.New()
color.Prompt = ""
color.CharLimit = MaxHexDigits
color.Placeholder = "000000"
+ color.Validate = func(s string) error {
+ if len(s) != 0 && len(s) != MaxHexDigits {
+ return errors.New("err")
+ }
+ return nil
+ }
return Model{
- Width: 50,
- Height: 10,
name: name,
icon: icon,
color: color,
- Style: style,
+ create: blurredCreate,
- precomputedIconStyle: lipgloss.NewStyle().Width(lipgloss.Width(name.View()) / 2),
+ nameWidth: nameWidth,
+ precomputedIconStyle: lipgloss.NewStyle().Width(nameWidth / 2),
}
}
@@ -99,17 +132,31 @@ func (m Model) Init() tea.Cmd {
func (m Model) View() string {
name := m.name.View()
- iconInput := underlineStyle(m.icon.View(), MaxIconLength, m.selected == IconField)
+ color := colors.Gray
+ if m.icon.Err != nil {
+ color = colors.Error
+ } else if m.selected == IconField {
+ color = colors.Focus
+ }
+ iconInput := underlineStyle(m.icon.View(), MaxIconLength, color)
iconText := m.precomputedIconStyle.Render(lipgloss.JoinHorizontal(lipgloss.Top, iconHeader, iconInput))
- colorInput := underlineStyle(m.color.View(), MaxHexDigits, m.selected == ColorField)
+ color = colors.Gray
+ if m.color.Err != nil {
+ color = colors.Error
+ } else if m.selected == ColorField {
+ color = colors.Focus
+ }
+ colorInput := underlineStyle(m.color.View(), MaxHexDigits, color)
colorText := lipgloss.JoinHorizontal(lipgloss.Top, colorHeader, colorInput)
icon := lipgloss.JoinHorizontal(lipgloss.Top, iconText, colorText)
- content := lipgloss.JoinVertical(lipgloss.Left, name, "\n", icon)
- popup := lipgloss.NewStyle().Width(m.Width).Height(m.Height).Render(content)
- return m.Style.Render(popup)
+ // create := lipgloss.NewStyle().Width(m.nameWidth).Align(lipgloss.Center).Render(m.create)
+ create := m.create
+
+ content := flex.NewVertical(name, icon, create).WithGap(1).View()
+ return style.Render(content)
}
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
@@ -130,7 +177,25 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
case IconField:
m.icon, cmd = m.icon.Update(msg)
case ColorField:
+ oldValue := m.color.Value()
+ position := m.color.Position()
m.color, cmd = m.color.Update(msg)
+ newValue := m.color.Value()
+
+ hex := "0123456789abcdefABCDEF"
+ invalid := false
+ for _, c := range newValue {
+ if !strings.ContainsRune(hex, c) {
+ invalid = true
+ break
+ }
+ }
+
+ if invalid {
+ m.color.SetValue(oldValue)
+ m.color.SetCursor(position)
+ }
+
}
return m, cmd
}
@@ -153,6 +218,7 @@ func (m *Model) updateFocus() tea.Cmd {
m.name.Blur()
m.icon.Blur()
m.color.Blur()
+ m.create = blurredCreate
switch m.selected {
case NameField:
return m.name.Focus()
@@ -160,8 +226,27 @@ func (m *Model) updateFocus() tea.Cmd {
return m.icon.Focus()
case ColorField:
return m.color.Focus()
+ case CreateField:
+ m.create = focusedCreate
+ return nil
default:
assert.Never("missing switch statement field in update focus", "selected", m.selected)
return nil
}
}
+
+func (m *Model) Select() tea.Cmd {
+ if m.selected != CreateField {
+ return nil
+ }
+
+ m.name.Input.Err = m.name.Input.Validate(m.name.Input.Value())
+ m.icon.Err = m.icon.Validate(m.icon.Value())
+ m.color.Err = m.color.Validate(m.color.Value())
+ if m.name.Input.Err != nil || m.icon.Err != nil || m.color.Err != nil {
+ return nil
+ }
+
+ // TODO: send api request for creating the server
+ return nil
+}
diff --git a/internal/client/ui/layouts/flex/flex.go b/internal/client/ui/layouts/flex/flex.go
index 6f39ec1..def7a18 100644
--- a/internal/client/ui/layouts/flex/flex.go
+++ b/internal/client/ui/layouts/flex/flex.go
@@ -16,8 +16,18 @@ type Model struct {
Vertical bool
}
-func New() Model {
- return Model{}
+func NewHorizontal(contents ...string) Model {
+ return Model{
+ contents: contents,
+ Vertical: false,
+ }
+}
+
+func NewVertical(contents ...string) Model {
+ return Model{
+ contents: contents,
+ Vertical: true,
+ }
}
func (m Model) Init() tea.Cmd {
@@ -54,3 +64,8 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
func (m *Model) SetContents(contents ...string) {
m.contents = contents
}
+
+func (m Model) WithGap(gap int) Model {
+ m.Gap = gap
+ return m
+}