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>