arnaucube
arnaucube/jellyfin-toolsPublic Self-hosted repository
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 { color: var(--accent); font-size: 1.25rem; font-weight: 700; font-variant-numeric: tabular-nums; }
47  .platform-bar { display: flex; height: .35rem; margin-top: .6rem; border-radius: 1rem; box-shadow: 0 0 12px rgba(var(--accent-rgb), .22); overflow: hidden; }
48  .shows { width: 100%; background: linear-gradient(90deg, rgba(var(--accent-rgb), .45), var(--accent)); }
49  .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
50</style>