Blog/Components/Pages/Rvrb.razor 10.6 K · 271 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>
53 <SpotifyLink Kind="track" Id="@track.SpotifyTrackId" Name="@track.Name"/>
54 </h3>
55 <p><ArtistLinks Names="track.ArtistNames" Ids="track.SpotifyArtistIds"/></p>
56 @* A bar needs both halves to mean anything: a track that came without a
57 duration, or one already playing when the bot connected, gets neither. *@
58 @if (track.Duration is { } length && track.Elapsed is not null)
59 {
60 <p>
61 <progress id="trackProgress" max="1000"
62 value="@((int)((track.Progress ?? 0) * 1000))"
63 data-elapsed-ms="@((long)track.Elapsed.Value.TotalMilliseconds)"
64 data-duration-ms="@((long)length.TotalMilliseconds)"
65 data-age-ms="@((long)Age.TotalMilliseconds)"></progress>
66 <span id="trackElapsed">@Format.Duration(track.Elapsed)</span>
67 /
68 <span>@Format.Duration(length)</span>
69 </p>
70 }
71 <p>
72 👍 @Snapshot.Live.Dopes
73 🔖 @Snapshot.Live.Stars
74 @if (Snapshot.Live.AutoDoped)
75 {
76 <span class="status-up"> — the bot doped this one</span>
77 }
78 @if (Snapshot.Live.AutoStarred)
79 {
80 <span class="status-up"> — and starred it</span>
81 }
82 </p>
83 </div>
84 </div>
85 }
86 </Panel>
87
88 @if (Snapshot.Live is { Djs.Count: > 0 } live)
89 {
90 <Panel Legend="@($"DJ queue — one lap ≈ {Format.Duration(live.Lap)}")">
91 <div class="table-scroll">
92 <table>
93 <thead>
94 <tr>
95 <th>DJ</th>
96 <th>Avg track length</th>
97 <th>Next play in</th>
98 </tr>
99 </thead>
100 <tbody>
101 @foreach (var dj in live.Djs)
102 {
103 <tr>
104 <td>@(dj.Current ? "▶ " : "")@(dj.Name ?? "someone")</td>
105 <td>
106 ≈ @Format.Duration(dj.AverageTrack)
107 @if (!dj.Measured)
108 {
109 <span class="muted"> (assumed)</span>
110 }
111 </td>
112 <td>≈ @Format.Duration(dj.Wait)</td>
113 </tr>
114 }
115 </tbody>
116 </table>
117 </div>
118 @if (live.QueuedTracks > 0)
119 {
120 <p>@live.QueuedTracks @(live.QueuedTracks == 1 ? "track" : "tracks") queued for the bot's own turn.</p>
121 }
122 </Panel>
123 }
124
125 <Panel Legend="Totals">
126 <dl class="stats">
127 <div><dt>Plays</dt><dd>@Snapshot.Totals.Plays</dd></div>
128 <div><dt>Tracks</dt><dd>@Snapshot.Totals.Tracks</dd></div>
129 <div><dt>Artists</dt><dd>@Snapshot.Totals.Artists</dd></div>
130 <div><dt>DJs</dt><dd>@Snapshot.Totals.Djs</dd></div>
131 <div><dt>Users seen</dt><dd>@Snapshot.Totals.Users</dd></div>
132 <div><dt>Dopes</dt><dd>@Snapshot.Totals.Dopes</dd></div>
133 <div><dt>Stars</dt><dd>@Snapshot.Totals.Stars</dd></div>
134 </dl>
135 @if (Snapshot.Totals.FirstPlayAt is { } first)
136 {
137 <p>Recording plays since <Timestamp At="first"/>.</p>
138 }
139 </Panel>
140
141 <Panel Legend="@($"Plays per day — last {Snapshot.PlaysPerDay.Count} days")">
142 <ol class="bars">
143 @foreach (var day in Snapshot.PlaysPerDay)
144 {
145 <li title="@BarTitle(day)">
146 <span class="bar" style="block-size: @(BarHeight(day.Plays))%"></span>
147 <span class="bar-label">@day.Plays</span>
148 <span class="bar-day"><span class="day-abbrev">@DayLabel(day)</span><span class="day-initial">@DayInitial(day)</span></span>
149 </li>
150 }
151 </ol>
152 </Panel>
153
154 <Panel Legend="Top DJs">
155 <div class="table-scroll">
156 <table>
157 <thead>
158 <tr>
159 <th>DJ</th>
160 <th>Plays</th>
161 <th>👍</th>
162 <th>🔖</th>
163 <th>Score</th>
164 </tr>
165 </thead>
166 <tbody>
167 @foreach (var dj in Snapshot.TopDjs)
168 {
169 <tr>
170 <td>@dj.Name</td>
171 <td>@dj.Plays</td>
172 <td>@dj.Dopes</td>
173 <td>@dj.Stars</td>
174 <td>@dj.Score</td>
175 </tr>
176 }
177 </tbody>
178 </table>
179 </div>
180 </Panel>
181
182 <Panel Legend="Top tracks">
183 <div class="table-scroll">
184 <table>
185 <thead>
186 <tr>
187 <th>Track</th>
188 <th>Artist</th>
189 <th>Plays</th>
190 <th>Score</th>
191 </tr>
192 </thead>
193 <tbody>
194 @foreach (var track in Snapshot.TopTracks)
195 {
196 <tr>
197 <td>
198 <SpotifyLink Kind="track" Id="@track.SpotifyTrackId" Name="@track.TrackName"/>
199 </td>
200 <td><ArtistLinks Names="track.ArtistNames" Ids="track.SpotifyArtistIds"/></td>
201 <td>@track.Plays</td>
202 <td>@track.Score</td>
203 </tr>
204 }
205 </tbody>
206 </table>
207 </div>
208 </Panel>
209
210 <Panel Legend="Top artists">
211 <div class="table-scroll">
212 <table>
213 <thead>
214 <tr>
215 <th>Artist</th>
216 <th>Plays</th>
217 <th>Score</th>
218 </tr>
219 </thead>
220 <tbody>
221 @foreach (var artist in Snapshot.TopArtists)
222 {
223 <tr>
224 <td>
225 <SpotifyLink Kind="artist" Id="@artist.SpotifyArtistId" Name="@artist.ArtistName"/>
226 </td>
227 <td>@artist.Plays</td>
228 <td>@artist.Score</td>
229 </tr>
230 }
231 </tbody>
232 </table>
233 </div>
234 </Panel>
235
236 <Panel Legend="Recently played">
237 <div class="table-scroll">
238 <table>
239 <thead>
240 <tr>
241 <th>When</th>
242 <th>DJ</th>
243 <th>Track</th>
244 <th>Artist</th>
245 <th>Score</th>
246 </tr>
247 </thead>
248 <tbody>
249 @foreach (var play in Snapshot.RecentPlays)
250 {
251 <tr>
252 <td><Timestamp At="play.PlayedAt"/></td>
253 <td>@play.Dj</td>
254 <td>
255 <SpotifyLink Kind="track" Id="@play.SpotifyTrackId" Name="@play.TrackName"/>
256 </td>
257 <td><ArtistLinks Names="play.ArtistNames" Ids="play.SpotifyArtistIds"/></td>
258 <td>@play.Score</td>
259 </tr>
260 }
261 </tbody>
262 </table>
263 </div>
264 </Panel>
265
266 <p class="muted">
267 Read from <code>@Options.Node</code> over Erlang distribution with
268 <a href="https://github.com/Besselking/BeamSharp">BeamSharp</a>. Snapshot taken <Timestamp At="Snapshot.GeneratedAt"/>.
269 </p>
270 }
271 </main>