tui/card.bend
cswap-bend-cli@0.1.0.0 · 226 lines · 43 definitions
1import Base
2import cswap-bend-cli@0.1.0.0/tui/fmt.bend as F
3import cswap-bend-cli@0.1.0.0/tui/model.bend as M
4import cswap-bend-cli@0.1.0.0/usage/norm.bend as UNorm
5
6
7
8
9def plain(+t: String) -> F.Seg:
10 F.sg(t, F.RPlain{})
11
12def muted(+t: String) -> F.Seg:
13 F.sg(t, F.RMuted{})
14
15def cat(+a: List<&2, F.Seg>, +b: List<&2, F.Seg>) -> List<&2, F.Seg>:
16 List.append(&2, F.Seg, a, b)
17
18def alias_of(+a: M.Acct) -> String:
19 match a:
20 case M.Acct{s, e, al, t, ac, d, w, g, n}:
21 al
22
23def names_pick(+bare: Bool, +a: M.Acct) -> List<&2, F.Seg>:
24 match bare:
25 case True{}:
26 [plain(M.acct_email(a))]
27 case False{}:
28 [F.sg(alias_of(a), F.RAccentB{}), plain(" (" ++ M.acct_email(a) ++ ")")]
29
30
31def names(+a: M.Acct) -> List<&2, F.Seg>:
32 names_pick(String.is_empty(alias_of(a)), a)
33
34def tag_of(+a: M.Acct) -> String:
35 match a:
36 case M.Acct{s, e, al, t, ac, d, w, g, n}:
37 t
38
39def flag_if(+c: Bool, +s: F.Seg) -> List<&2, F.Seg>:
40 match c:
41 case True{}:
42 [s]
43 case False{}:
44 []
45
46
47def age_note(+g: Maybe<&2, U32>) -> List<&2, F.Seg>:
48 match g:
49 case None{}:
50 []
51 case Some{s}:
52 flag_if(U32.is_ge(s, 180), muted(" · " ++ F.duration(s) ++ " ago"))
53
54def is_stale(+g: Maybe<&2, U32>) -> Bool:
55 match g:
56 case None{}:
57 False{}
58 case Some{s}:
59 U32.is_gt(s, 300)
60
61def tail_flags(+a: M.Acct) -> List<&2, F.Seg>:
62 cat(flag_if(M.acct_active(a), F.sg(" ● active", F.RAccentB{})), cat(flag_if(M.acct_disabled(a), muted(" (disabled)")), age_note(M.acct_age(a))))
63
64def head_line(+a: M.Acct) -> List<&2, F.Seg>:
65 cat([F.sg(F.pad_left(M.acct_slot(a), 2n) ++ " ", F.RBold{})], cat(names(a), cat([muted(" [" ++ tag_of(a) ++ "]")], tail_flags(a))))
66
67
68
69def label_width(+ws: List<&2, M.Win>, +acc: Nat) -> Nat:
70 match ws:
71 case Nil{}:
72 acc
73 case Con{w, t}:
74 label_width(t, Nat.max(acc, String.length(M.win_label(w))))
75
76def bar_width(+width: U32, +lw: Nat) -> Nat:
77 Nat.max(12n, Nat.min(30n, Nat.sub(Nat.sub(U32.to_nat(width), 42n), lw)))
78
79
80def reset_short(+r: Maybe<&2, U32>, +now: U32) -> String:
81 match r:
82 case None{}:
83 ""
84 case Some{t}:
85 F.sel.s(U32.is_gt(t, now), "resets " ++ F.duration((t - now : U32)), "resets now")
86
87
88def reset_full(+r: Maybe<&2, U32>, +now: U32, +off: U32) -> String:
89 match r:
90 case None{}:
91 ""
92 case Some{t}:
93 F.sel.s(U32.is_gt(t, now), reset_short(r, now) ++ " · " ++ UNorm.clock.text(t, now, off), reset_short(r, now))
94
95def is_model(+w: M.Win) -> Bool:
96 Bool.not(String.eq(M.win_label(w), "5h")) && Bool.not(String.eq(M.win_label(w), "7d"))
97
98def is_full(+p: Maybe<&2, U32>) -> Bool:
99 match p:
100 case None{}:
101 False{}
102 case Some{c}:
103 U32.is_ge(c, 10000)
104
105def maxed(+w: M.Win) -> Bool:
106 is_model(w) && is_full(M.win_pct(w))
107
108def mark(+w: M.Win, +s: String) -> String:
109 F.sel.s(maxed(w), F.sel.s(String.is_empty(s), "(!)", s ++ " (!)"), s)
110
111def pick_suffix(+full: String, +short: String, +room: Nat) -> String:
112 F.sel.s(Nat.is_le(String.length(full), room), full, short)
113
114
115def suffix(+w: M.Win, +now: U32, +off: U32, +room: Nat) -> String:
116 mark(w, pick_suffix(reset_full(M.win_reset(w), now, off), reset_short(M.win_reset(w), now), room))
117
118def pct_seg(+p: Maybe<&2, U32>, +stale: Bool) -> F.Seg:
119 match p:
120 case None{}:
121 muted(" usage unknown")
122 case Some{c}:
123 F.Seg{" " ++ F.pct_text(c), F.severity(c), stale}
124
125def pad2(+s: String) -> String:
126 F.sel.s(String.is_empty(s), "", " " ++ s)
127
128def view_width(+v: M.View) -> U32:
129 match v:
130 case M.View{w, c}:
131 w
132
133
134def room(+v: M.View, +lw: Nat, +bw: Nat) -> Nat:
135 Nat.sub(U32.to_nat(view_width(v)), Nat.add(Nat.add(4n, lw), Nat.add(Nat.add(1n, bw), 7n)))
136
137
138def row_head(+w: M.Win, +lw: Nat, +bw: Nat, +thr: Maybe<&2, U32>, +stale: Bool) -> List<&2, F.Seg>:
139 cat([plain(" "), muted(F.pad_right(M.win_label(w), lw) ++ " ")], F.bar(M.win_pct(w), bw, thr, stale))
140
141
142def row(+w: M.Win, +lw: Nat, +bw: Nat, +v: M.View, +thr: Maybe<&2, U32>, +stale: Bool, +now: U32, +off: U32) -> List<&2, F.Seg>:
143 cat(row_head(w, lw, bw, thr, stale), [pct_seg(M.win_pct(w), stale), muted(pad2(suffix(w, now, off, room(v, lw, bw))))])
144
145def rows(+ws: List<&2, M.Win>, +lw: Nat, +bw: Nat, +v: M.View, +thr: Maybe<&2, U32>, +stale: Bool, +now: U32, +off: U32) -> List<&2, List<&2, F.Seg>>:
146 match ws:
147 case Nil{}:
148 Nil{}
149 case Con{w, t}:
150 Con{row(w, lw, bw, v, thr, stale, now, off), rows(t, lw, bw, v, thr, stale, now, off)}
151
152def unavailable(+a: M.Acct) -> List<&2, F.Seg>:
153 [plain(" "), muted("usage unavailable" ++ F.sel.s(String.is_empty(M.acct_note(a)), "", " · " ++ M.acct_note(a)))]
154
155def card_rows(+a: M.Acct, +ws: List<&2, M.Win>, +v: M.View, +thr: Maybe<&2, U32>, +now: U32, +off: U32) -> List<&2, List<&2, F.Seg>>:
156 match ws:
157 case Nil{}:
158 [unavailable(a)]
159 case _:
160 rows(ws, label_width(ws, 0n), bar_width(view_width(v), label_width(ws, 0n)), v, thr, is_stale(M.acct_age(a)), now, off)
161
162
163def card(+a: M.Acct, +v: M.View, +thr: Maybe<&2, U32>, +now: U32, +off: U32) -> List<&2, List<&2, F.Seg>>:
164 Con{head_line(a), card_rows(a, M.acct_wins(a), v, thr, now, off)}
165
166
167
168def reset_in(+p: Maybe<&2, U32>, +r: Maybe<&2, U32>, +now: U32) -> List<&2, F.Seg>:
169 flag_if(is_full(p) && Bool.not(String.is_empty(reset_short(r, now))), muted(" (" ++ reset_short(r, now) ++ ")"))
170
171def pct_part(+w: M.Win, +c: U32, +now: U32) -> List<&2, F.Seg>:
172 cat([muted(M.win_label(w) ++ " "), F.sg(U32.show(F.round_even(c)) ++ "%", F.severity(c))], reset_in(M.win_pct(w), M.win_reset(w), now))
173
174def part_pick(+model: Bool, +full: Bool, +w: M.Win, +p: Maybe<&2, U32>, +now: U32) -> List<&2, F.Seg>:
175 match model p:
176 case True{} _:
177 flag_if(full, F.sg(M.win_label(w) ++ " (!)", F.RCrit{}))
178 case False{} None{}:
179 []
180 case False{} Some{c}:
181 pct_part(w, c, now)
182
183def mini_part(+w: M.Win, +now: U32) -> List<&2, F.Seg>:
184 part_pick(is_model(w), maxed(w), w, M.win_pct(w), now)
185
186def push_part(+p: List<&2, F.Seg>, +rest: List<&2, List<&2, F.Seg>>) -> List<&2, List<&2, F.Seg>>:
187 match p:
188 case Nil{}:
189 rest
190 case _:
191 Con{p, rest}
192
193def mini_parts(+ws: List<&2, M.Win>, +now: U32) -> List<&2, List<&2, F.Seg>>:
194 match ws:
195 case Nil{}:
196 Nil{}
197 case Con{w, t}:
198 push_part(mini_part(w, now), mini_parts(t, now))
199
200def joined_rest(+ps: List<&2, List<&2, F.Seg>>) -> List<&2, F.Seg>:
201 match ps:
202 case Nil{}:
203 []
204 case Con{p, t}:
205 cat([F.sg(" · ", F.RTrack{})], cat(p, joined_rest(t)))
206
207def joined(+ps: List<&2, List<&2, F.Seg>>) -> List<&2, F.Seg>:
208 match ps:
209 case Nil{}:
210 []
211 case Con{p, t}:
212 cat(p, joined_rest(t))
213
214def mini_body(+a: M.Acct, +ps: List<&2, List<&2, F.Seg>>) -> List<&2, F.Seg>:
215 match ps:
216 case Nil{}:
217 [muted(F.sel.s(String.is_empty(M.acct_note(a)), "usage unknown", M.acct_note(a)))]
218 case _:
219 joined(ps)
220
221def mini_head(+a: M.Acct) -> List<&2, F.Seg>:
222 cat([F.sg(F.pad_left(M.acct_slot(a), 2n) ++ " ", F.RBold{})], cat(names(a), cat([muted(" [" ++ tag_of(a) ++ "]")], flag_if(M.acct_disabled(a), muted(" (disabled)")))))
223
224
225def mini(+a: M.Acct, +now: U32) -> List<&2, F.Seg>:
226 cat(mini_head(a), cat([plain(" ")], mini_body(a, mini_parts(M.acct_wins(a), now))))