From be151fd6f7e7860cc60bee67bab815c155e86fcd Mon Sep 17 00:00:00 2001 From: Fuwn Date: Fri, 29 May 2026 23:05:37 +0000 Subject: feat(analytics): track umami events across core actions Add a guarded track() wrapper (src/lib/analytics.ts) so programmatic events no-op instead of throwing when umami is absent (localhost), lazily loaded, or ad-blocked. Instrument the core loop (progress increment, roulette, list filter, force refresh), the auth funnel (log in/out, command palette), settings toggles/selects at the wrapper level, sync/debug/feed actions, and tool/sharing actions. --- src/lib/List/Anime/CleanAnimeList.svelte | 13 ++++++++++++- src/lib/List/Manga/CleanMangaList.svelte | 11 ++++++++++- src/lib/List/MediaRoulette.svelte | 4 ++++ 3 files changed, 26 insertions(+), 2 deletions(-) (limited to 'src/lib/List') diff --git a/src/lib/List/Anime/CleanAnimeList.svelte b/src/lib/List/Anime/CleanAnimeList.svelte index e27378eb..c8bf1b7d 100644 --- a/src/lib/List/Anime/CleanAnimeList.svelte +++ b/src/lib/List/Anime/CleanAnimeList.svelte @@ -21,6 +21,7 @@ import stateBin from "$stores/stateBin"; import localforage from "localforage"; import MediaRoulette from "../MediaRoulette.svelte"; import type { Title } from "../mediaTitle"; +import { track } from "$lib/analytics"; export let media: Media[]; export let title: Title; @@ -117,6 +118,10 @@ $: if (browser && !dummy && media && previousAnimeList !== media) const updateSelectedList = (event: Event) => { const nextSelectedList = (event.currentTarget as HTMLSelectElement).value; + track("Filter Anime List", { + scope: nextSelectedList === "All" ? "all" : "custom", + }); + selectedList = nextSelectedList; if (!disableFilter && $stateBin[filterKey] !== nextSelectedList) @@ -213,6 +218,8 @@ onDestroy(() => clearAiringRefreshTimeout()); const increment = (anime: Media, progress: number) => { if (dummy || pendingUpdate === anime.id) return; + track("Increment Anime Progress"); + pendingUpdate = anime.id; lastUpdatedMedia = anime.id; @@ -247,6 +254,7 @@ const increment = (anime: Media, progress: number) => { class="small-button" onclick={() => (showRoulette = true)} title={$locale().lists.actions?.pickRandomAnime} + data-umami-event="Open Anime Roulette" > Roulette @@ -255,7 +263,10 @@ const increment = (anime: Media, progress: number) => { {#if media.length === 0} {$locale().lists.empty?.anime} - {:else if $settings.displayMediaListFilter && !disableFilter && hasDistinguishingList} diff --git a/src/lib/List/Manga/CleanMangaList.svelte b/src/lib/List/Manga/CleanMangaList.svelte index 25e6d48f..af52ca02 100644 --- a/src/lib/List/Manga/CleanMangaList.svelte +++ b/src/lib/List/Manga/CleanMangaList.svelte @@ -31,6 +31,7 @@ import CleanList from "../CleanList.svelte"; import stateBin from "$stores/stateBin"; import localforage from "localforage"; import MediaRoulette from "../MediaRoulette.svelte"; +import { track } from "$lib/analytics"; export let media: Media[]; export let cleanCache: () => void; @@ -105,6 +106,10 @@ $: filteredMedia = const updateSelectedList = (event: Event) => { const nextSelectedList = (event.currentTarget as HTMLSelectElement).value; + track("Filter Manga List", { + scope: nextSelectedList === "All" ? "all" : "custom", + }); + selectedList = nextSelectedList; if (!disableFilter && $stateBin[filterKey] !== nextSelectedList) @@ -125,8 +130,11 @@ $: if (rateLimited && !serviceStatusResponse) serviceStatusResponse = getMangadexServiceStatus(); const increment = (manga: Media) => { - if (!(pendingUpdate === manga.id || dummy)) + if (!(pendingUpdate === manga.id || dummy)) { + track("Increment Manga Progress"); + updateMedia(manga.id, manga.mediaListEntry?.progress, media); + } }; @@ -154,6 +162,7 @@ const increment = (manga: Media) => { class="small-button" onclick={() => (showRoulette = true)} title={$locale().lists.actions?.pickRandomManga} + data-umami-event="Open Manga Roulette" > Roulette diff --git a/src/lib/List/MediaRoulette.svelte b/src/lib/List/MediaRoulette.svelte index dc9a2269..64c585df 100644 --- a/src/lib/List/MediaRoulette.svelte +++ b/src/lib/List/MediaRoulette.svelte @@ -5,6 +5,7 @@ import { outboundLink } from "$lib/Media/links"; import settings from "$stores/settings"; import locale from "$stores/locale"; import { mediaTitle } from "./mediaTitle"; +import { track } from "$lib/analytics"; interface Props { media: Media[]; @@ -25,6 +26,8 @@ let currentMedia = $derived(media[displayIndex]); const startRoulette = () => { if (media.length === 0 || isSpinning) return; + track("Spin Roulette", { type }); + isSpinning = true; showResult = false; selectedIndex = Math.floor(Math.random() * media.length); @@ -131,6 +134,7 @@ const handleOverlayClick = (e: MouseEvent) => { href={outboundLink(currentMedia, type, $settings.displayOutboundLinksTo)} target="_blank" class="view-link" + data-umami-event="Roulette View Media" > {$locale({ values: { -- cgit v1.2.3