arnaucube
arnaucube/jellyfin-toolsPublic Self-hosted repository
jellyfin-tools/jellyfin-year-review/web/src/lib/ViewingTimeline.svelte
jellyfin-year-review/web/src/lib/ViewingTimeline.svelteRaw
 1<script>
 2  import Poster from './Poster.svelte';
 3
 4  let { items, year, kind } = $props();
 5  const monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
 6  let titles = $derived([...items].sort((a, b) => a.title.localeCompare(b.title) || a.id.localeCompare(b.id)));
 7  let groups = $derived(monthNames.map((name, i) => ({
 8    month: i + 1, name, items: titles.filter(item => item.months?.includes(i + 1))
 9  })).filter(group => group.items.length));
10  // Keep older exports usable without inventing viewing dates for their titles.
11  let undated = $derived(titles.filter(item => !item.months?.length));
12</script>
13
14{#if items.length}
15  <div class="viewing-timeline" aria-label="{kind} watched by month">
16    {#each groups as group (group.month)}
17      <section class="timeline-month" aria-label="{group.name} {year}">
18        <h3><time datetime="{year}-{String(group.month).padStart(2, '0')}">{group.name} {year}</time></h3>
19        <ul class="timeline-titles">
20          {#each group.items as item (item.id)}
21            <li><Poster {item} /><h4>{item.title}</h4></li>
22          {/each}
23        </ul>
24      </section>
25    {/each}
26    {#if undated.length}
27      <section class="timeline-month" aria-label="Viewing dates unavailable">
28        <h3>Viewing dates unavailable</h3>
29        <ul class="timeline-titles">
30          {#each undated as item (item.id)}
31            <li><Poster {item} /><h4>{item.title}</h4></li>
32          {/each}
33        </ul>
34      </section>
35    {/if}
36  </div>
37{:else}
38  <p class="story-lede">No {kind.toLowerCase()} watched in {year}.</p>
39{/if}
40
41<style>
42  .viewing-timeline { margin-top: 32px; padding-left: 24px; border-left: 2px solid currentColor; }
43  .timeline-month { position: relative; padding-bottom: 28px; }
44  .timeline-month:last-child { padding-bottom: 0; }
45  .timeline-month::before { content: ''; position: absolute; width: 10px; height: 10px; border-radius: 50%; background: currentColor; left: -30px; top: 6px; }
46  h3 { font-size: 1.2rem; margin-bottom: 16px; }
47  .timeline-titles { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr)); gap: 16px 24px; }
48  li { display: flex; align-items: center; gap: 14px; min-width: 0; }
49  h4 { margin: 0; font-size: 1rem; line-height: 1.5; overflow-wrap: anywhere; }
50  @media (max-width: 760px) { .viewing-timeline { padding-left: 18px; } .timeline-month::before { left: -24px; } }
51</style>