Skip to content

Commit 795c4db

Browse files
committed
feat(inputs): add realtime validate
add realtime validate Signed-off-by: mritd <[email protected]>
1 parent 14d69ed commit 795c4db

File tree

3 files changed

+120
-38
lines changed

3 files changed

+120
-38
lines changed

Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ install:
1515
go install -trimpath -ldflags "-X 'main.version=${BUILD_VERSION}' \
1616
-X 'main.buildDate=${BUILD_DATE}' \
1717
-X 'main.commitID=${COMMIT_SHA1}'"
18+
gitflow-toolkit install
1819
debug:
1920
go install -trimpath -gcflags "all=-N -l"
21+
gitflow-toolkit install
2022

2123
clean:
2224
rm -rf dist

ui_commit.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ func (m commitModel) commit() tea.Msg {
8989

9090
msg := commitMsg{
9191
Type: m.views[SELECTOR].(selectorModel).choice,
92-
Scope: m.views[INPUTS].(inputsModel).inputs[0].Value(),
93-
Subject: m.views[INPUTS].(inputsModel).inputs[1].Value(),
94-
Body: m.views[INPUTS].(inputsModel).inputs[2].Value(),
95-
Footer: m.views[INPUTS].(inputsModel).inputs[3].Value(),
92+
Scope: m.views[INPUTS].(inputsModel).inputs[0].input.Value(),
93+
Subject: m.views[INPUTS].(inputsModel).inputs[1].input.Value(),
94+
Body: m.views[INPUTS].(inputsModel).inputs[2].input.Value(),
95+
Footer: m.views[INPUTS].(inputsModel).inputs[3].input.Value(),
9696
SOB: sob,
9797
}
9898

ui_commit_inputs.go

Lines changed: 114 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package main
22

33
import (
4-
"fmt"
4+
"errors"
55
"strings"
6+
"time"
7+
8+
"github.com/charmbracelet/bubbles/spinner"
69

710
"github.com/charmbracelet/bubbles/textinput"
811
tea "github.com/charmbracelet/bubbletea"
@@ -56,12 +59,32 @@ var (
5659
Background(lipgloss.AdaptiveColor{Light: "#DDDDDD", Dark: "#626262"}).
5760
Padding(0, 1, 0, 1).
5861
Bold(true)
62+
63+
inputsErrLayout = lipgloss.NewStyle().
64+
Padding(0, 0, 0, 1)
65+
66+
inputsErrStyle = lipgloss.NewStyle().
67+
Foreground(lipgloss.Color("#FFFDF5")).
68+
Background(lipgloss.Color("#FF6037")).
69+
Padding(0, 1, 0, 1).
70+
Bold(true)
71+
72+
spinnerMetaFrame1 = lipgloss.NewStyle().Foreground(lipgloss.Color("1")).Render("❯")
73+
spinnerMetaFrame2 = lipgloss.NewStyle().Foreground(lipgloss.Color("3")).Render("❯")
74+
spinnerMetaFrame3 = lipgloss.NewStyle().Foreground(lipgloss.Color("2")).Render("❯")
5975
)
6076

77+
type inputWithCheck struct {
78+
input textinput.Model
79+
checker func(s string) error
80+
}
81+
6182
type inputsModel struct {
6283
focusIndex int
6384
title string
64-
inputs []textinput.Model
85+
inputs []inputWithCheck
86+
err error
87+
errSpinner spinner.Model
6588
}
6689

6790
func (m inputsModel) Init() tea.Cmd {
@@ -79,8 +102,19 @@ func (m inputsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
79102
case "tab", "shift+tab", "enter", "up", "down":
80103
s := msg.String()
81104

82-
if s == "enter" && m.focusIndex == len(m.inputs) {
83-
return m, func() tea.Msg { return done{nextView: COMMIT} }
105+
if s == "enter" {
106+
if m.focusIndex == len(m.inputs) {
107+
for _, iwc := range m.inputs {
108+
if iwc.checker != nil {
109+
m.err = iwc.checker(iwc.input.Value())
110+
if m.err != nil {
111+
return m, spinner.Tick
112+
}
113+
}
114+
}
115+
return m, func() tea.Msg { return done{nextView: COMMIT} }
116+
}
117+
84118
}
85119

86120
// Cycle indexes
@@ -100,22 +134,26 @@ func (m inputsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
100134
for i := 0; i <= len(m.inputs)-1; i++ {
101135
if i == m.focusIndex {
102136
// Set focused state
103-
cmds[i] = m.inputs[i].Focus()
104-
m.inputs[i].PromptStyle = inputsPromptStyle
105-
m.inputs[i].TextStyle = inputsTextStyle
137+
cmds[i] = m.inputs[i].input.Focus()
138+
m.inputs[i].input.PromptStyle = inputsPromptStyle
139+
m.inputs[i].input.TextStyle = inputsTextStyle
106140
continue
107141
}
108142
// Remove focused state
109-
m.inputs[i].Blur()
110-
m.inputs[i].PromptStyle = inputsPromptNormalStyle
111-
m.inputs[i].TextStyle = inputsTextNormalStyle
143+
m.inputs[i].input.Blur()
144+
m.inputs[i].input.PromptStyle = inputsPromptNormalStyle
145+
m.inputs[i].input.TextStyle = inputsTextNormalStyle
112146
}
113147

114148
return m, tea.Batch(cmds...)
115149
}
116150
case string:
117151
m.title = "✔ Commit Type: " + msg
118152
return m, nil
153+
case spinner.TickMsg:
154+
var cmd tea.Cmd
155+
m.errSpinner, cmd = m.errSpinner.Update(msg)
156+
return m, cmd
119157
}
120158

121159
// Handle character input and blinking
@@ -125,12 +163,17 @@ func (m inputsModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
125163
}
126164

127165
func (m *inputsModel) updateInputs(msg tea.Msg) tea.Cmd {
128-
var cmds = make([]tea.Cmd, len(m.inputs))
166+
var cmds = make([]tea.Cmd, len(m.inputs)+1)
129167

130168
// Only text inputs with Focus() set will respond, so it's safe to simply
131169
// update all of them here without any further logic.
132170
for i := range m.inputs {
133-
m.inputs[i], cmds[i] = m.inputs[i].Update(msg)
171+
m.inputs[i].input, cmds[i] = m.inputs[i].input.Update(msg)
172+
}
173+
174+
if m.focusIndex < len(m.inputs) && m.inputs[m.focusIndex].checker != nil {
175+
m.err = m.inputs[m.focusIndex].checker(m.inputs[m.focusIndex].input.Value())
176+
cmds[len(m.inputs)] = spinner.Tick
134177
}
135178

136179
return tea.Batch(cmds...)
@@ -140,7 +183,7 @@ func (m inputsModel) View() string {
140183
var b strings.Builder
141184

142185
for i := range m.inputs {
143-
b.WriteString(m.inputs[i].View())
186+
b.WriteString(m.inputs[i].input.View())
144187
if i < len(m.inputs)-1 {
145188
b.WriteRune('\n')
146189
}
@@ -150,7 +193,12 @@ func (m inputsModel) View() string {
150193
if m.focusIndex == len(m.inputs) {
151194
button = inputsButtonStyle.Render("➜ Submit")
152195
}
153-
_, _ = fmt.Fprint(&b, inputsButtonBlockStyle.Render(button))
196+
197+
if m.err != nil {
198+
b.WriteString(inputsButtonBlockStyle.Render(button + inputsErrLayout.Render(m.errSpinner.View()+" "+inputsErrStyle.Render(m.err.Error()))))
199+
} else {
200+
b.WriteString(inputsButtonBlockStyle.Render(button))
201+
}
154202

155203
title := inputsTitleBarStyle.Render(inputsTitleStyle.Render(m.title))
156204
inputs := inputsBlockStyle.Render(b.String())
@@ -160,37 +208,69 @@ func (m inputsModel) View() string {
160208

161209
func newInputsModel() inputsModel {
162210
m := inputsModel{
163-
inputs: make([]textinput.Model, 4),
211+
inputs: make([]inputWithCheck, 4),
164212
}
165213

166-
var t textinput.Model
167214
for i := range m.inputs {
168-
t = textinput.NewModel()
169-
t.CursorStyle = inputsCursorStyle
170-
t.CharLimit = 128
215+
var iwc inputWithCheck
216+
217+
iwc.input = textinput.NewModel()
218+
iwc.input.CursorStyle = inputsCursorStyle
219+
iwc.input.CharLimit = 128
171220

172221
switch i {
173222
case 0:
174-
t.Prompt = "1. SCOPE "
175-
t.Placeholder = "Specifying place of the commit change."
176-
t.PromptStyle = inputsPromptStyle
177-
t.TextStyle = inputsTextStyle
178-
t.Focus()
223+
iwc.input.Prompt = "1. SCOPE "
224+
iwc.input.Placeholder = "Specifying place of the commit change."
225+
iwc.input.PromptStyle = inputsPromptStyle
226+
iwc.input.TextStyle = inputsTextStyle
227+
iwc.input.Focus()
228+
iwc.checker = func(s string) error {
229+
if strings.TrimSpace(s) == "" {
230+
return errors.New("Scope cannot be empty")
231+
}
232+
return nil
233+
}
179234
case 1:
180-
t.Prompt = "2. SUBJECT "
181-
t.PromptStyle = inputsPromptNormalStyle
182-
t.Placeholder = "A very short description of the change."
235+
iwc.input.Prompt = "2. SUBJECT "
236+
iwc.input.PromptStyle = inputsPromptNormalStyle
237+
iwc.input.Placeholder = "A very short description of the change."
238+
iwc.checker = func(s string) error {
239+
if strings.TrimSpace(s) == "" {
240+
return errors.New("Subject cannot be empty")
241+
}
242+
return nil
243+
}
183244
case 2:
184-
t.Prompt = "3. BODY "
185-
t.PromptStyle = inputsPromptNormalStyle
186-
t.Placeholder = "Motivation and contrasts for the change."
245+
iwc.input.Prompt = "3. BODY "
246+
iwc.input.PromptStyle = inputsPromptNormalStyle
247+
iwc.input.Placeholder = "Motivation and contrasts for the change."
187248
case 3:
188-
t.Prompt = "4. FOOTER "
189-
t.PromptStyle = inputsPromptNormalStyle
190-
t.Placeholder = "Description of the change, justification and migration notes."
249+
iwc.input.Prompt = "4. FOOTER "
250+
iwc.input.PromptStyle = inputsPromptNormalStyle
251+
iwc.input.Placeholder = "Description of the change, justification and migration notes."
191252
}
192253

193-
m.inputs[i] = t
254+
m.inputs[i] = iwc
255+
}
256+
257+
m.errSpinner = spinner.NewModel()
258+
m.errSpinner.Spinner = spinner.Spinner{
259+
Frames: []string{
260+
// "❯ "
261+
spinnerMetaFrame1 + " ",
262+
// "❯❯ "
263+
spinnerMetaFrame1 + spinnerMetaFrame2 + " ",
264+
// "❯❯❯ "
265+
spinnerMetaFrame1 + spinnerMetaFrame2 + spinnerMetaFrame3 + " ",
266+
// " ❯❯❯"
267+
" " + spinnerMetaFrame1 + spinnerMetaFrame2 + spinnerMetaFrame3,
268+
// " ❯❯"
269+
" " + spinnerMetaFrame1 + spinnerMetaFrame2,
270+
// " ❯"
271+
" " + spinnerMetaFrame1,
272+
},
273+
FPS: time.Second / 10,
194274
}
195275

196276
return m

0 commit comments

Comments
 (0)