Skip to content

Commit fe60343

Browse files
committed
refactor(ui): refactor runewidth code
refactor runewidth code Signed-off-by: mritd <[email protected]>
1 parent 9cb7c7b commit fe60343

File tree

7 files changed

+256
-168
lines changed

7 files changed

+256
-168
lines changed

main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"os"
66
"os/exec"
77
"path/filepath"
8+
9+
"github.com/mattn/go-runewidth"
810
)
911

1012
var (
@@ -35,3 +37,8 @@ func main() {
3537
os.Exit(1)
3638
}
3739
}
40+
41+
// See also: https://github.com/charmbracelet/lipgloss/issues/40#issuecomment-891167509
42+
func init() {
43+
runewidth.DefaultCondition.EastAsianWidth = false
44+
}

ui.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package main
22

33
import (
4-
tea "github.com/charmbracelet/bubbletea"
5-
"github.com/mattn/go-runewidth"
64
"strings"
5+
6+
tea "github.com/charmbracelet/bubbletea"
77
)
88

99
const (
@@ -13,10 +13,6 @@ const (
1313
RESULT
1414
)
1515

16-
func init() {
17-
runewidth.DefaultCondition.EastAsianWidth = false
18-
}
19-
2016
type done struct {
2117
err error
2218
}

ui/multi_task.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
package ui
2+
3+
import (
4+
"time"
5+
6+
"github.com/charmbracelet/bubbles/spinner"
7+
tea "github.com/charmbracelet/bubbletea"
8+
"github.com/charmbracelet/lipgloss"
9+
)
10+
11+
var (
12+
MultiTaskLayoutStyle = lipgloss.NewStyle().
13+
Padding(0, 0, 1, 2)
14+
15+
MultiTaskBorderStyle = lipgloss.NewStyle().
16+
Border(lipgloss.RoundedBorder()).
17+
BorderForeground(lipgloss.Color("#37B9FF")).
18+
Width(55).
19+
Padding(0, 1, 1, 2)
20+
21+
MultiTaskMsgSuccessStyle = lipgloss.NewStyle().
22+
Bold(true).
23+
Foreground(lipgloss.Color("#25A065"))
24+
25+
MultiTaskMsgFailedStyle = MultiTaskMsgSuccessStyle.Copy().
26+
Foreground(lipgloss.Color("#EE6FF8"))
27+
28+
MultiTaskMsgWaitingStyle = MultiTaskMsgSuccessStyle.Copy().
29+
Foreground(lipgloss.Color("#37B9FF"))
30+
31+
MultiTaskSpinner = spinner.Model{
32+
Style: lipgloss.NewStyle().Foreground(lipgloss.Color("#f8ca61")),
33+
Spinner: spinner.Spinner{
34+
Frames: []string{
35+
"[ ]",
36+
"[= ]",
37+
"[== ]",
38+
"[=== ]",
39+
"[ === ]",
40+
"[ ===]",
41+
"[ ==]",
42+
"[ =]",
43+
"[ ]",
44+
"[ =]",
45+
"[ ==]",
46+
"[ ===]",
47+
"[ === ]",
48+
"[=== ]",
49+
"[== ]",
50+
"[= ]",
51+
"[ ]",
52+
},
53+
FPS: time.Second / 10,
54+
}}
55+
)
56+
57+
type TaskFunc func() error
58+
59+
type TaskDoneMsg struct{}
60+
61+
type Task struct {
62+
Title string
63+
Func TaskFunc
64+
65+
err error
66+
}
67+
68+
type MultiTaskModel struct {
69+
Tasks []Task
70+
Spinner spinner.Model
71+
72+
TaskDelay time.Duration
73+
LayoutStyle lipgloss.Style
74+
BorderStyle lipgloss.Style
75+
MsgSuccessStyle lipgloss.Style
76+
MsgFailedStyle lipgloss.Style
77+
MsgWaitingStyle lipgloss.Style
78+
79+
index int
80+
}
81+
82+
func NewMultiTaskModel() MultiTaskModel {
83+
return NewMultiTaskModelWithTasks(nil)
84+
}
85+
86+
func NewMultiTaskModelWithTasks(tasks []Task) MultiTaskModel {
87+
return MultiTaskModel{
88+
Tasks: tasks,
89+
Spinner: MultiTaskSpinner,
90+
TaskDelay: 300 * time.Millisecond,
91+
LayoutStyle: MultiTaskLayoutStyle,
92+
BorderStyle: MultiTaskBorderStyle,
93+
MsgSuccessStyle: MultiTaskMsgSuccessStyle,
94+
MsgFailedStyle: MultiTaskMsgFailedStyle,
95+
MsgWaitingStyle: MultiTaskMsgWaitingStyle,
96+
}
97+
}
98+
99+
func (m MultiTaskModel) Init() tea.Cmd {
100+
return tea.Batch(spinner.Tick, func() tea.Msg {
101+
if len(m.Tasks) == 0 {
102+
panic("The number of tasks cannot be 0")
103+
}
104+
return m.Tasks[0]
105+
})
106+
}
107+
108+
func (m MultiTaskModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
109+
switch msg.(type) {
110+
case Task:
111+
return m, func() tea.Msg {
112+
if m.TaskDelay > 0 {
113+
time.Sleep(m.TaskDelay)
114+
}
115+
m.Tasks[m.index].err = msg.(Task).Func()
116+
return TaskDoneMsg{}
117+
}
118+
case TaskDoneMsg:
119+
if m.Tasks[m.index].err != nil {
120+
return m, tea.Quit
121+
}
122+
123+
m.index++
124+
return m, func() tea.Msg {
125+
if m.index == len(m.Tasks) {
126+
return tea.Quit()
127+
}
128+
return m.Tasks[m.index]
129+
}
130+
default:
131+
var cmd tea.Cmd
132+
m.Spinner, cmd = m.Spinner.Update(msg)
133+
return m, cmd
134+
}
135+
136+
}
137+
138+
func (m MultiTaskModel) View() string {
139+
var view string
140+
for i, task := range m.Tasks {
141+
if task.err == nil {
142+
if i < m.index {
143+
view = lipgloss.JoinVertical(lipgloss.Left, view, MultiTaskMsgSuccessStyle.Render("[ ✔ ] "+task.Title))
144+
} else {
145+
view = lipgloss.JoinVertical(lipgloss.Left, view, m.Spinner.View()+" "+MultiTaskMsgWaitingStyle.Render(task.Title))
146+
}
147+
} else {
148+
view = lipgloss.JoinVertical(lipgloss.Left, view, MultiTaskMsgFailedStyle.Render("[ ✗ ] "+task.err.Error()))
149+
}
150+
151+
}
152+
return MultiTaskLayoutStyle.Render(MultiTaskBorderStyle.Render(view))
153+
}
154+
155+
//func main() {
156+
// m := MultiTaskModel{
157+
// Tasks: []Task{
158+
// {
159+
// Title: "Clean install dir...",
160+
// Func: func() error { return nil },
161+
// },
162+
// {
163+
// Title: "Clean symlinks...",
164+
// Func: func() error { return nil },
165+
// },
166+
// {
167+
// Title: "Unset commit hooks...",
168+
// Func: func() error { return nil },
169+
// },
170+
// {
171+
// Title: "Create toolkit home...",
172+
// Func: func() error { return nil },
173+
// },
174+
// {
175+
// Title: "Install executable file...",
176+
// Func: func() error { return nil },
177+
// },
178+
// {
179+
// Title: "Create symlink...",
180+
// Func: func() error { return errors.New("This is a test message.") },
181+
// },
182+
// {
183+
// Title: "Install success...",
184+
// Func: func() error { return nil },
185+
// },
186+
// },
187+
// Spinner: MultiTaskSpinner,
188+
// }
189+
// if err := tea.NewProgram(&m).Start(); err != nil {
190+
// fmt.Printf("could not start program: %s\n", err)
191+
// os.Exit(1)
192+
// }
193+
//}

ui/multi_task_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package ui
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/mattn/go-runewidth"
8+
9+
tea "github.com/charmbracelet/bubbletea"
10+
)
11+
12+
func init() {
13+
// Some users may need to turn off the EastAsianWidth option to make the UI display correctly
14+
// See also: https://github.com/charmbracelet/lipgloss/issues/40#issuecomment-891167509
15+
runewidth.DefaultCondition.EastAsianWidth = false
16+
}
17+
18+
func TestMultiTaskModel(t *testing.T) {
19+
m := NewMultiTaskModelWithTasks([]Task{
20+
{
21+
Title: "Clean install dir...",
22+
Func: NothingFunc,
23+
},
24+
{
25+
Title: "Clean symlinks...",
26+
Func: NothingFunc,
27+
},
28+
{
29+
Title: "Unset commit hooks...",
30+
Func: NothingFunc,
31+
},
32+
{
33+
Title: "Create toolkit home...",
34+
Func: NothingFunc,
35+
},
36+
{
37+
Title: "Install executable file...",
38+
Func: NothingFunc,
39+
},
40+
{
41+
Title: "Create symlink...",
42+
Func: func() error { return errors.New("This is a test message.") },
43+
},
44+
{
45+
Title: "Install success...",
46+
Func: NothingFunc,
47+
},
48+
})
49+
if err := tea.NewProgram(&m).Start(); err != nil {
50+
t.Fatal(err)
51+
}
52+
}

ui/single_task.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package main

ui/single_task_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package ui

0 commit comments

Comments
 (0)