jellyfin-tools/jellyfin-year-review/web/src/lib/PlatformBreakdown.svelte
jellyfin-year-review/web/src/lib/PlatformBreakdown.svelteRaw
1<script>
2 import { t, locale } from './i18n.js';
3
4 let { platforms = [], records = 0 } = $props();
5 // Normalize existing exports too, keeping their fallback row after named services.
6 let rows = $derived(platforms.map(row => ({
7 ...row, name: row.name === 'Unknown platform' ? 'Other' : row.name
8 })).sort((a, b) => Number(a.name === 'Other') - Number(b.name === 'Other')));
9 const count = value => new Intl.NumberFormat(locale).format(value);
10 let maximum = $derived(Math.max(1, ...platforms.map(row => row.shows)));
11</script>
12
13{#if platforms.length}
14 <div class="platform-list">
15 <table>
16 <caption class="sr-only">{t('TV shows watched by platform')}</caption>
17 <thead><tr><th scope="col">{t('Platform')}</th><th scope="col">{t('TV shows')}</th></tr></thead>
18 <tbody>
19 {#each rows as platform}
20 <tr>
21 <th scope="row">
22 <span>{platform.name === 'Other' ? t('Other') : platform.name}</span>
23 <div class="platform-bar" aria-hidden="true" style:width="{100 * platform.shows / maximum}%">
24 <span class="shows"></span>
25 </div>
26 </th>
27 <td>{count(platform.shows)}</td>
28 </tr>
29 {/each}
30 </tbody>
31 </table>
32 </div>
33{:else}
34 <p class="empty-note">{records ? t('Platform data is unavailable for this review. Regenerate the year to include it.') : t('No TV shows watched this year.')}</p>
35{/if}
36
37<style>
38 .platform-list { max-height: 26rem; overflow: auto; margin-top: 1.5rem; }
39 table { width: 100%; border-collapse: collapse; table-layout: fixed; }
40 th, td { padding: .8rem .4rem; border-bottom: 1px solid #ffffff1f; }
41 th { text-align: left; }
42 thead th { font-size: .8rem; color: var(--muted, #b9bac9); }
43 th:first-child { width: 54%; }
44 thead th:not(:first-child), td { text-align: right; }
45 tbody th { font-weight: 600; overflow-wrap: anywhere; }
46 td { font-variant-numeric: tabular-nums; }
47 .platform-bar { display: flex; height: .35rem; margin-top: .6rem; border-radius: 1rem; overflow: hidden; }
48 .shows { width: 100%; background: #69dfce; }
49 .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
50</style>