jevstrudel.git / website / src / jev / activity.mjs

What is happening on the site, for the jev panel's activity tab (ActivityTab.jsx), from GET /jev/data/activity (worker/src/activity.ts): pages of items, newest first, each page from before the last one's oldest. Every field is someone else's writing, shown as text.

5export const ACTIVITY = '/jev/data/activity';
6export const PAGE = 30;
8export async function loadActivity(before = null, fetcher = fetch) {
9  const q = new URLSearchParams({ limit: String(PAGE), ...(before ? { before: String(before) } : {}) });
10  const res = await fetcher(`${ACTIVITY}?${q}`, { headers: { Accept: 'application/json' } });
11  if (!res.ok) throw new Error(`the site answered ${res.status}`);
12  const body = await res.json();
13  return { items: Array.isArray(body.items) ? body.items : [], next: typeof body.next === 'number' ? body.next : null };
14}

Pages joined: an item already shown (same kind, song or id, and time) is not shown twice when a page repeats its edge.

18export function appendPage(items, page) {
19  const key = (i) => `${i.kind}:${i.id ?? i.song ?? ''}:${i.rev ?? ''}:${i.at}`;
20  const seen = new Set(items.map(key));
21  return [...items, ...page.filter((i) => !seen.has(key(i)))];
22}
24const pct = (x) => `${Math.round(x * 100)}%`;
25const VERDICT = { pending: 'waiting on Jev', fine: 'public', spam: 'held by Jev as spam', abuse: 'held by Jev as abuse' };

An item in words, beside its author's name (a take's player's): what they did, and Jev's part in it. title(songId) names a site song.

29export function describe(item, title = (id) => id) {
30  switch (item.kind) {
31    case 'revision': {
32      const what = item.rev > 1 ? `revised “${item.title}” (rev ${item.rev})` : `published “${item.title}”`;
33      const art = item.art !== null && item.art !== undefined ? `, ${pct(item.art)} art` : '';
34      return `${what}: ${VERDICT[item.screen] ?? item.screen}${art}`;
35    }
36    case 'scored':
37      return `“${item.title}” (rev ${item.rev}) scored ${pct(item.art)} art by Jev`;
38    case 'comment': {
39      const on = item.on?.site ? title(item.on.site) : `“${item.on?.title ?? 'a listener’s song'}”`;
40      return item.body === null ? `commented on ${on}: ${VERDICT[item.screen] ?? item.screen}` : `commented on ${on}`;
41    }
42    case 'pitch':
43      return item.body === null ? `pitched a song: ${VERDICT[item.screen] ?? item.screen}` : 'pitched a song';
44    case 'take': {
45      const song = item.song.startsWith('listener:') ? `“${item.title ?? 'a listener’s song'}”` : title(item.song);
46      const ended = item.ended === 'finished' ? 'to its end' : 'and stopped';
47      return `played ${song}, ${item.sections} ${item.sections === 1 ? 'section' : 'sections'} ${ended}`;
48    }
49    default:
50      return '';
51  }
52}