Skip to content

Commit

Permalink
tlock: Rework form widget implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
eklairs committed May 10, 2024
1 parent da8b733 commit f15a4f8
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 180 deletions.
250 changes: 70 additions & 180 deletions tlock-internal/form/form.go
Original file line number Diff line number Diff line change
@@ -1,178 +1,31 @@
package form

import (
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/eklairs/tlock/tlock-internal/components"
"github.com/eklairs/tlock/tlock-internal/utils"
tlockstyles "github.com/eklairs/tlock/tlock/styles"
)

// Form item
type FormItem interface {
// Handle messages
Update(msg tea.Msg) (FormItem, tea.Cmd)

// View
View() string

// Focus
Focus() FormItem

// Unfocus
Unfocus() FormItem

// Returns the value
Value() string
}

// ==== Form Item Input Box Start ====

// Form item for input boxes
type FormItemInputBox struct {
// Title
Title string

// Description
Description string

// Input box
Input textinput.Model

// Error message
ErrorMessage *error
}

// Update
func (item FormItemInputBox) Update(msg tea.Msg) (FormItem, tea.Cmd) {
var cmd tea.Cmd

item.Input, cmd = item.Input.Update(msg)

return item, cmd
}

// Focus
func (item FormItemInputBox) Focus() FormItem {
item.Input.Focus()

return item
}

// Unfocus
func (item FormItemInputBox) Unfocus() FormItem {
item.Input.Blur()

return item
}

// View
func (item FormItemInputBox) View() string {
return components.InputGroup(item.Title, item.Description, item.ErrorMessage, item.Input)
}

// Value
func (item FormItemInputBox) Value() string {
return item.Input.Value()
}

// ==== Form Item Input Box End ====

// ==== Form Item Option Box Start ====

// Form item for option box
type FormItemOptionBox struct {
// Title
Title string

// Description
Description string

// Input box
Values []string

// Selected value
SelectedIndex int

// Error message
Focused bool
}

func (item FormItemOptionBox) Value() string {
return item.Values[item.SelectedIndex]
}

// Update
func (item FormItemOptionBox) Update(msg tea.Msg) (FormItem, tea.Cmd) {
switch msgType := msg.(type) {
case tea.KeyMsg:
switch msgType.String() {
case "right":
if item.SelectedIndex != len(item.Values)-1 {
item.SelectedIndex += 1
}

case "left":
if item.SelectedIndex != 0 {
item.SelectedIndex -= 1
}
}
}

return item, nil
}

// Focus
func (item FormItemOptionBox) Focus() FormItem {
item.Focused = true

return item
// Message that represents that the form is submitted and the items pass the validators
type FormSubmittedMsg struct {
Data map[string]string
}

// Unfocus
func (item FormItemOptionBox) Unfocus() FormItem {
item.Focused = false

return item
}

// View
func (item FormItemOptionBox) View() string {
renderables := make([]string, len(item.Values))

for index, option := range item.Values {
if index == item.SelectedIndex {
if item.Focused {
renderables[index] = tlockstyles.Styles.AccentBgItem.Render(option)
} else {
renderables[index] = tlockstyles.Styles.SubAltBg.Render(option)
}
} else {
renderables[index] = tlockstyles.Styles.SubTextItem.Render(option)
}

renderables[index] += " "
}

ui := lipgloss.JoinVertical(
lipgloss.Left,
tlockstyles.Styles.Title.Render(item.Title),
tlockstyles.Styles.SubText.Render(item.Description), "",
lipgloss.JoinHorizontal(lipgloss.Left, renderables...),
)

return lipgloss.NewStyle().Width(31).Render(ui)
}

// ==== Form Item Option Box End ====
// Validator
type Validator func(value string) error

// Form item wrapped
type FormItemWrapped struct {
// ID
ID string

// Form item
FormItem FormItem

// Is it enabled
Enabled bool

// Validators
Validators []Validator
}

type Form struct {
Expand All @@ -185,36 +38,28 @@ type Form struct {

// Initializes a new form item
func New(items []FormItem) Form {
// Map
wrapped := utils.Map(items, func(item FormItem) FormItemWrapped { return FormItemWrapped{FormItem: item, Enabled: true} })

// Return instance
form := Form{
Items: wrapped,
}

// Focus first item
form.Items[0].FormItem = form.Items[0].FormItem.Focus()

// Return
return form
return Form{}
}

// Switches focus from one index to another
func (form *Form) switchFocus(old, new int) {
form.Items[old].FormItem = form.Items[old].FormItem.Unfocus()
form.Items[new].FormItem = form.Items[new].FormItem.Focus()
// Do focus changing
form.Items[old].FormItem.Unfocus()
form.Items[new].FormItem.Focus()

// Set new index
form.FocusedIndex = new
}

// Updates
func (form *Form) Update(msg tea.Msg) {
func (form *Form) Update(msg tea.Msg) tea.Cmd {
cmds := make([]tea.Cmd, 0)

switch msgType := msg.(type) {
case tea.KeyMsg:
switch msgType.String() {
match_key: switch msgType.String() {
case "tab":
if form.FocusedIndex != len(form.Items)-1 {
if form.FocusedIndex != len(form.Items)-1 && form.FocusedItem().ShouldGoNext() {
next := form.FocusedIndex + 1

// If the next is disabled, then switch to its next
Expand All @@ -226,7 +71,7 @@ func (form *Form) Update(msg tea.Msg) {
form.switchFocus(form.FocusedIndex, next)
}
case "shift+tab":
if form.FocusedIndex != 0 {
if form.FocusedIndex != 0 && form.FocusedItem().ShouldGoPrev() {
next := form.FocusedIndex - 1

// If the next is disabled, then switch to its next
Expand All @@ -237,8 +82,53 @@ func (form *Form) Update(msg tea.Msg) {
// Change focus
form.switchFocus(form.FocusedIndex, next)
}
default:
form.Items[form.FocusedIndex].FormItem, _ = form.Items[form.FocusedIndex].FormItem.Update(msg)
case "enter":
data := make(map[string]string)

// Validate them all!
for _, item := range form.Items {
// Run validators
for _, validator := range item.Validators {
if err := validator(item.FormItem.Value()); err != nil {
// Set erro
item.FormItem.SetError(&err)

// There is issue with the form, break from the switch
break match_key
}
}

// Set the item
data[item.ID] = item.FormItem.Value()
}

// Return
cmds = append(cmds, func() tea.Msg { return FormSubmittedMsg{ Data: data } })
}
}

// Let the current form item handle
cmds = append(cmds, form.Items[form.FocusedIndex].FormItem.Update(msg))

// Return
return tea.Batch(cmds...)
}

// Returns the focused form item
func (form *Form) FocusedItem() FormItem {
return form.Items[form.FocusedIndex].FormItem
}

// Renders the form
func (form Form) View() string {
// Items
items := make([]string, len(form.Items))

// Render them all
for index, item := range form.Items {
items[index] = item.FormItem.View()
}

// Render
return lipgloss.JoinVertical(lipgloss.Center, items...)
}
34 changes: 34 additions & 0 deletions tlock-internal/form/formitem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package form

import tea "github.com/charmbracelet/bubbletea"

// Interface that every form item must implement
// Form item
type FormItem interface {
// Handle messages
Update(msg tea.Msg) tea.Cmd

// View
View() string

// Focus
Focus()

// Unfocus
Unfocus()

// Returns the value
Value() string

// Sets the error message
// Nil means to remove the error
SetError(err *error)

// Returns if the focus should go to the next item
// Useful for groupping
ShouldGoNext() bool

// Returns if the focus should go to the next item
// Useful for groupping
ShouldGoPrev() bool
}
Loading

0 comments on commit f15a4f8

Please sign in to comment.