Blog/Components/Pages/Rvrb.razor 9.9 K · 263 lines · raw · history

1 @page "/rvrb"
2 <PageTitle>rvrb bot</PageTitle>
3 <PageScript Src="./Components/Pages/Rvrb.razor.js"></PageScript>
4
5 <main>
6 <h1>rvrb bot</h1>
7
8 <p class="flex-spread">
9 <span class="@StatusClass">@StatusLabel</span>
10 <label class="noselect">
11 Auto-refresh
12 <input type="checkbox" id="autoRefresh">
13 </label>
14 </p>
15
16 @if (Snapshot is null)
17 {
18 <Panel Legend="Unreachable">
19 <p>
20 Nothing answered on the bot's node, so there are no stats to show.
21 </p>
22 @if (Status.Error is { } error)
23 {
24 @* A failure on the far side arrives as an Erlang term, stack trace and all, which
25 is worth keeping but not worth putting in front of everyone. *@
26 <details>
27 <summary class="status-down">What went wrong</summary>
28 <pre>@error</pre>
29 </details>
30 }
31 </Panel>
32 }
33 else
34 {
35 <Panel Legend="Now playing">
36 @if (Snapshot.Live is null)
37 {
38 <p>The bot isn't connected to RVRB right now, so nothing is playing.</p>
39 }
40 else if (Snapshot.Live.CurrentTrack is not { } track)
41 {
42 <p>Connected, but no track has played since the bot joined.</p>
43 }
44 else
45 {
46 <div class="now-playing">
47 @if (track.AlbumArt is { } art)
48 {
49 <img class="album-art" src="@art" alt="" width="128" height="128"/>
50 }
51 <div>
52 <h3>@track.Name</h3>
53 <p>@string.Join(", ", track.ArtistNames)</p>
54 @* A bar needs both halves to mean anything: a track that came without a
55 duration, or one already playing when the bot connected, gets neither. *@
56 @if (track.Duration is { } length && track.Elapsed is not null)
57 {
58 <p>
59 <progress id="trackProgress" max="1000"
60 value="@((int)((track.Progress ?? 0) * 1000))"
61 data-elapsed-ms="@((long)track.Elapsed.Value.TotalMilliseconds)"
62 data-duration-ms="@((long)length.TotalMilliseconds)"
63 data-age-ms="@((long)Age.TotalMilliseconds)"></progress>
64 <span id="trackElapsed">@Format.Duration(track.Elapsed)</span>
65 /
66 <span>@Format.Duration(length)</span>
67 </p>
68 }
69 <p>
70 👍 @Snapshot.Live.Dopes
71 🔖 @Snapshot.Live.Stars
72 @if (Snapshot.Live.AutoDoped)
73 {
74 <span class="status-up"> — the bot doped this one</span>
75 }
76 @if (Snapshot.Live.AutoStarred)
77 {
78 <span class="status-up"> — and starred it</span>
79 }
80 </p>
81 </div>
82 </div>
83 }
84 </Panel>
85
86 @if (Snapshot.Live is { Djs.Count: > 0 } live)
87 {
88 <Panel Legend="@($"DJ queue — one lap ≈ {Format.Duration(live.Lap)}")">
89 <div class="table-scroll">
90 <table>
91 <thead>
92 <tr>
93 <th>DJ</th>
94 <th>Avg track length</th>
95 <th>Next play in</th>
96 </tr>
97 </thead>
98 <tbody>
99 @foreach (var dj in live.Djs)
100 {
101 <tr>
102 <td>@(dj.Current ? "▶ " : "")@(dj.Name ?? "someone")</td>
103 <td>
104 ≈ @Format.Duration(dj.AverageTrack)
105 @if (!dj.Measured)
106 {
107 <span class="muted"> (assumed)</span>
108 }
109 </td>
110 <td>≈ @Format.Duration(dj.Wait)</td>
111 </tr>
112 }
113 </tbody>
114 </table>
115 </div>
116 @if (live.QueuedTracks > 0)
117 {
118 <p>@live.QueuedTracks @(live.QueuedTracks == 1 ? "track" : "tracks") queued for the bot's own turn.</p>
119 }
120 </Panel>
121 }
122
123 <Panel Legend="Totals">
124 <dl class="stats">
125 <div><dt>Plays</dt><dd>@Snapshot.Totals.Plays</dd></div>
126 <div><dt>Tracks</dt><dd>@Snapshot.Totals.Tracks</dd></div>
127 <div><dt>Artists</dt><dd>@Snapshot.Totals.Artists</dd></div>
128 <div><dt>DJs</dt><dd>@Snapshot.Totals.Djs</dd></div>
129 <div><dt>Users seen</dt><dd>@Snapshot.Totals.Users</dd></div>
130 <div><dt>Dopes</dt><dd>@Snapshot.Totals.Dopes</dd></div>
131 <div><dt>Stars</dt><dd>@Snapshot.Totals.Stars</dd></div>
132 </dl>
133 @if (Snapshot.Totals.FirstPlayAt is { } first)
134 {
135 <p>Recording plays since <Timestamp At="first"/>.</p>
136 }
137 </Panel>
138
139 <Panel Legend="@($"Plays per day — last {Snapshot.PlaysPerDay.Count} days")">
140 <ol class="bars">
141 @foreach (var day in Snapshot.PlaysPerDay)
142 {
143 <li title="@BarTitle(day)">
144 <span class="bar" style="block-size: @(BarHeight(day.Plays))%"></span>
145 <span class="bar-label">@day.Plays</span>
146 <span class="bar-day">@DayLabel(day)</span>
147 </li>
148 }
149 </ol>
150 </Panel>
151
152 <Panel Legend="Top DJs">
153 <div class="table-scroll">
154 <table>
155 <thead>
156 <tr>
157 <th>DJ</th>
158 <th>Plays</th>
159 <th>👍</th>
160 <th>🔖</th>
161 <th>Score</th>
162 </tr>
163 </thead>
164 <tbody>
165 @foreach (var dj in Snapshot.TopDjs)
166 {
167 <tr>
168 <td>@dj.Name</td>
169 <td>@dj.Plays</td>
170 <td>@dj.Dopes</td>
171 <td>@dj.Stars</td>
172 <td>@dj.Score</td>
173 </tr>
174 }
175 </tbody>
176 </table>
177 </div>
178 </Panel>
179
180 <Panel Legend="Top tracks">
181 <div class="table-scroll">
182 <table>
183 <thead>
184 <tr>
185 <th>Track</th>
186 <th>Artist</th>
187 <th>Plays</th>
188 <th>Score</th>
189 </tr>
190 </thead>
191 <tbody>
192 @foreach (var track in Snapshot.TopTracks)
193 {
194 <tr>
195 <td>@track.TrackName</td>
196 <td>@string.Join(", ", track.ArtistNames)</td>
197 <td>@track.Plays</td>
198 <td>@track.Score</td>
199 </tr>
200 }
201 </tbody>
202 </table>
203 </div>
204 </Panel>
205
206 <Panel Legend="Top artists">
207 <div class="table-scroll">
208 <table>
209 <thead>
210 <tr>
211 <th>Artist</th>
212 <th>Plays</th>
213 <th>Score</th>
214 </tr>
215 </thead>
216 <tbody>
217 @foreach (var artist in Snapshot.TopArtists)
218 {
219 <tr>
220 <td>@artist.ArtistName</td>
221 <td>@artist.Plays</td>
222 <td>@artist.Score</td>
223 </tr>
224 }
225 </tbody>
226 </table>
227 </div>
228 </Panel>
229
230 <Panel Legend="Recently played">
231 <div class="table-scroll">
232 <table>
233 <thead>
234 <tr>
235 <th>When</th>
236 <th>DJ</th>
237 <th>Track</th>
238 <th>Artist</th>
239 <th>Score</th>
240 </tr>
241 </thead>
242 <tbody>
243 @foreach (var play in Snapshot.RecentPlays)
244 {
245 <tr>
246 <td><Timestamp At="play.PlayedAt"/></td>
247 <td>@play.Dj</td>
248 <td>@play.TrackName</td>
249 <td>@string.Join(", ", play.ArtistNames)</td>
250 <td>@play.Score</td>
251 </tr>
252 }
253 </tbody>
254 </table>
255 </div>
256 </Panel>
257
258 <p class="muted">
259 Read from <code>@Options.Node</code> over Erlang distribution with
260 <a href="https://github.com/Besselking/BeamSharp">BeamSharp</a>. Snapshot taken <Timestamp At="Snapshot.GeneratedAt"/>.
261 </p>
262 }
263 </main>