-
Notifications
You must be signed in to change notification settings - Fork 1
/
spinner.gleam
109 lines (88 loc) · 2.8 KB
/
spinner.gleam
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import glearray.{type Array}
import repeatedly.{type Repeater}
import gleam_community/ansi
import gleam/io
const clear_line_code = "\u{001b}[2K"
const go_to_start_code = "\r"
pub const clock_frames = [
"🕛", "🕐", "🕑", "🕒", "🕓", "🕔", "🕕", "🕖", "🕗", "🕘",
"🕙", "🕚",
]
pub const half_circle_frames = ["◐", "◓", "◑", "◒"]
pub const moon_frames = [
"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘",
]
pub const negative_dots_frames = [
"⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷",
]
pub const snake_frames = [
"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏",
]
pub const triangle_frames = ["◢", "◣", "◤", "◥"]
pub const walking_frames = ["⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"]
pub opaque type Spinner {
Spinner(repeater: Repeater(State), frames: Array(String))
}
type State {
State(text: String, colour: fn(String) -> String)
}
pub opaque type Builder {
Builder(frames: List(String), text: String, colour: fn(String) -> String)
}
/// Start a spinner that runs concurrently in another Erlang process or
/// JavaScript task.
///
pub fn new(text: String) -> Builder {
Builder(snake_frames, text, ansi.pink)
}
pub fn with_frames(builder: Builder, frames: List(String)) -> Builder {
Builder(..builder, frames: frames)
}
pub fn with_colour(builder: Builder, colour: fn(String) -> String) -> Builder {
Builder(..builder, colour: colour)
}
pub fn start(builder: Builder) -> Spinner {
let frames = glearray.from_list(builder.frames)
let repeater =
repeatedly.call(80, State(builder.text, builder.colour), fn(state, i) {
print(frames, state, i)
state
})
Spinner(repeater, frames)
}
pub fn set_text(spinner: Spinner, text: String) -> Nil {
repeatedly.update_state(spinner.repeater, fn(state) {
State(..state, text: text)
})
}
pub fn set_colour(spinner: Spinner, colour: fn(String) -> String) -> Nil {
repeatedly.update_state(spinner.repeater, fn(state) {
State(..state, colour: colour)
})
}
/// Stop the spinner, clearing the terminal line and showing the cursor. You
/// may want to print a success message after this.
///
/// This should be called before your program ends to re-enable the terminal
/// cursor.
///
pub fn stop(spinner: Spinner) -> Nil {
repeatedly.stop(spinner.repeater)
let show_cursor = "\u{001b}[?25h"
io.print(clear_line_code <> go_to_start_code <> show_cursor)
}
fn print(frames: Array(String), state: State, index: Int) -> Nil {
let hide_cursor = "\u{001b}[?25l"
io.print(
hide_cursor
<> clear_line_code
<> go_to_start_code
<> state.colour(frame(frames, index))
<> " "
<> state.text,
)
}
fn frame(frames: Array(String), index: Int) -> String {
let assert Ok(frame) = glearray.get(frames, index % glearray.length(frames))
frame
}