From f292d3f9b6d3da5eec4d8712f0b6db3f177e1ffb Mon Sep 17 00:00:00 2001 From: Ewan Croft Date: Thu, 13 Aug 2026 15:35:47 +0100 Subject: [PATCH] feat(tourmaline): add dataset explorer with per-item detail modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports lastfm-stats-web's dataset table: a sortable/searchable table over the top-50 artists/tracks/albums with CSV export, plus a click-through detail modal showing animated stats (scrobbles, weeks active, best rank, best month) and an artist rank-over-time line chart. Scoped to the aggregator's existing top-50 cap rather than the full listening history — lifting that cap would need aggregator.ts changes that are out of scope here. Registers Chart.js's LineController, needed for the new rank-over-time chart and not previously used by any chart. Co-Authored-By: Claude Sonnet 5 --- packages/tourmaline/src/lib/chart.ts | 2 + .../src/routes/profile/[did]/+page.svelte | 10 + .../src/routes/profile/[did]/CountUp.svelte | 41 ++++ .../src/routes/profile/[did]/Dataset.svelte | 231 ++++++++++++++++++ .../profile/[did]/DatasetItemModal.svelte | 144 +++++++++++ .../profile/[did]/DatasetRankChart.svelte | 85 +++++++ 6 files changed, 513 insertions(+) create mode 100644 packages/tourmaline/src/routes/profile/[did]/CountUp.svelte create mode 100644 packages/tourmaline/src/routes/profile/[did]/Dataset.svelte create mode 100644 packages/tourmaline/src/routes/profile/[did]/DatasetItemModal.svelte create mode 100644 packages/tourmaline/src/routes/profile/[did]/DatasetRankChart.svelte diff --git a/packages/tourmaline/src/lib/chart.ts b/packages/tourmaline/src/lib/chart.ts index d1a9e24..4eb4338 100644 --- a/packages/tourmaline/src/lib/chart.ts +++ b/packages/tourmaline/src/lib/chart.ts @@ -11,6 +11,7 @@ import { BarController, BarElement, RadarController, + LineController, PointElement, LineElement, RadialLinearScale, @@ -25,6 +26,7 @@ Chart.register( BarController, BarElement, RadarController, + LineController, PointElement, LineElement, RadialLinearScale, diff --git a/packages/tourmaline/src/routes/profile/[did]/+page.svelte b/packages/tourmaline/src/routes/profile/[did]/+page.svelte index 3029751..3046457 100644 --- a/packages/tourmaline/src/routes/profile/[did]/+page.svelte +++ b/packages/tourmaline/src/routes/profile/[did]/+page.svelte @@ -49,6 +49,7 @@ import HabitsTab from './HabitsTab.svelte'; import CatalogueTab from './CatalogueTab.svelte'; import PunchcardHeatmap from './PunchcardHeatmap.svelte'; + import Dataset from './Dataset.svelte'; import EddingtonChart from './EddingtonChart.svelte'; import Recommendations from './Recommendations.svelte'; import TrackPreview from '$lib/components/TrackPreview.svelte'; @@ -512,6 +513,15 @@ {:else if activeTab === 'catalogue'} + +
diff --git a/packages/tourmaline/src/routes/profile/[did]/CountUp.svelte b/packages/tourmaline/src/routes/profile/[did]/CountUp.svelte new file mode 100644 index 0000000..1cc04c7 --- /dev/null +++ b/packages/tourmaline/src/routes/profile/[did]/CountUp.svelte @@ -0,0 +1,41 @@ + + +{formatter(display)} diff --git a/packages/tourmaline/src/routes/profile/[did]/Dataset.svelte b/packages/tourmaline/src/routes/profile/[did]/Dataset.svelte new file mode 100644 index 0000000..ba111a1 --- /dev/null +++ b/packages/tourmaline/src/routes/profile/[did]/Dataset.svelte @@ -0,0 +1,231 @@ + + +{#snippet sortIcon(key: SortKey)} + {#if sortKey === key} + {#if sortDir === 'asc'} + + {:else} + + {/if} + {:else} + + {/if} +{/snippet} + +
+
+
+

Dataset Explorer

+

+ Search, sort, and export your top 50 artists, tracks, and albums +

+
+
+ +
+
+ +
+
+ {#each CATEGORIES as cat (cat.id)} + + {/each} +
+ +
+ + +
+ + +
+ +
+ + + + + + {#if category !== 'artists'} + + {/if} + + + + + {#each rows as row (category + row.rank + row.name)} + (selected = row)} + > + + + {#if category !== 'artists'} + + {/if} + + + {/each} + {#if rows.length === 0} + + + + {/if} + +
+ + + + + + + +
{row.rank}{row.name}{row.artist}{row.count.toLocaleString()}
+ No matches +
+
+
+ +{#if selected} + (selected = null)} + /> +{/if} diff --git a/packages/tourmaline/src/routes/profile/[did]/DatasetItemModal.svelte b/packages/tourmaline/src/routes/profile/[did]/DatasetItemModal.svelte new file mode 100644 index 0000000..de98034 --- /dev/null +++ b/packages/tourmaline/src/routes/profile/[did]/DatasetItemModal.svelte @@ -0,0 +1,144 @@ + + + + +
e.key === 'Enter' && onClose()} + role="button" + tabindex="-1" +> +
e.stopPropagation()} + onkeydown={(e) => e.stopPropagation()} + role="dialog" + aria-modal="true" + aria-labelledby="dataset-modal-title" + tabindex="-1" + > +
+
+

{row.name}

+ {#if row.artist} +

{row.artist}

+ {/if} +
+ +
+ +
+
+

Total scrobbles

+

+ +

+
+ + {#if isArtist && weeksActive !== null} +
+

Weeks active

+

+ +

+
+ {/if} + + {#if isArtist && bestRank !== null} +
+

Best rank ever

+

+ # +

+
+ {/if} + + {#if isArtist && bestMonth !== null} +
+

Best month

+

+ +

+
+ {/if} +
+ + {#if isArtist && history.length > 0} +
+

Rank over time

+ +
+ {/if} +
+
diff --git a/packages/tourmaline/src/routes/profile/[did]/DatasetRankChart.svelte b/packages/tourmaline/src/routes/profile/[did]/DatasetRankChart.svelte new file mode 100644 index 0000000..d4cfd84 --- /dev/null +++ b/packages/tourmaline/src/routes/profile/[did]/DatasetRankChart.svelte @@ -0,0 +1,85 @@ + + +
+ +
-- 2.51.2