diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/Announcement.svelte | 4 | ||||
| -rw-r--r-- | src/lib/CommandPalette/CommandPalette.svelte | 11 | ||||
| -rw-r--r-- | src/lib/CommandPalette/authActions.ts | 5 | ||||
| -rw-r--r-- | src/lib/LandingHero.svelte | 3 | ||||
| -rw-r--r-- | src/lib/List/Anime/CleanAnimeList.svelte | 13 | ||||
| -rw-r--r-- | src/lib/List/Manga/CleanMangaList.svelte | 11 | ||||
| -rw-r--r-- | src/lib/List/MediaRoulette.svelte | 4 | ||||
| -rw-r--r-- | src/lib/Notification/Notification.svelte | 1 | ||||
| -rw-r--r-- | src/lib/Settings/Categories/Debug.svelte | 6 | ||||
| -rw-r--r-- | src/lib/Settings/Categories/Display.svelte | 27 | ||||
| -rw-r--r-- | src/lib/Settings/Categories/RSSFeeds.svelte | 1 | ||||
| -rw-r--r-- | src/lib/Settings/Categories/SettingSync.svelte | 4 | ||||
| -rw-r--r-- | src/lib/Settings/SettingCheckboxToggle.svelte | 13 | ||||
| -rw-r--r-- | src/lib/Settings/SettingToggle.svelte | 20 | ||||
| -rw-r--r-- | src/lib/Tools/ActivityHistory/Tool.svelte | 4 | ||||
| -rw-r--r-- | src/lib/Tools/Tracker/Tool.svelte | 12 | ||||
| -rw-r--r-- | src/lib/analytics.ts | 11 |
17 files changed, 126 insertions, 24 deletions
diff --git a/src/lib/Announcement.svelte b/src/lib/Announcement.svelte index 8bbdfe67..4338f8ff 100644 --- a/src/lib/Announcement.svelte +++ b/src/lib/Announcement.svelte @@ -47,7 +47,9 @@ const maxWidth = (input: string, max = 100) => { <Spacer /> - <button onclick={dismiss} class="dismiss">{dismissButton || 'Dismiss'}</button> + <button onclick={dismiss} class="dismiss" data-umami-event="Dismiss Announcement" + >{dismissButton || 'Dismiss'}</button + > </Popup> {/if} diff --git a/src/lib/CommandPalette/CommandPalette.svelte b/src/lib/CommandPalette/CommandPalette.svelte index 04eabff9..bf568200 100644 --- a/src/lib/CommandPalette/CommandPalette.svelte +++ b/src/lib/CommandPalette/CommandPalette.svelte @@ -4,6 +4,7 @@ import { fly, fade } from "svelte/transition"; import { flip } from "svelte/animate"; import type { CommandPaletteAction } from "./actions"; import locale from "$stores/locale"; +import { track } from "$lib/analytics"; export let items: CommandPaletteAction[] = []; export let open = false; @@ -103,6 +104,8 @@ $: if (open && !isVisible) { } const executeItem = (item: CommandPaletteAction) => { + track("Run Command", { command: item.name }); + if (item.onClick) item.onClick(); if (!item.preventDefault) window.location.href = item.url; @@ -158,7 +161,11 @@ const handleGlobalKey = (e: KeyboardEvent) => { open = !open; - if (open) requestAnimationFrame(() => inputRef?.focus()); + if (open) { + track("Open Command Palette"); + + requestAnimationFrame(() => inputRef?.focus()); + } } }; </script> @@ -214,6 +221,8 @@ const handleGlobalKey = (e: KeyboardEvent) => { out:fly={{ y: -20, duration: 150 }} animate:flip={{ duration: 200 }} onclick={(e) => { + track('Run Command', { command: item.name }); + if (item.preventDefault) e.preventDefault(); if (item.onClick) item.onClick(); diff --git a/src/lib/CommandPalette/authActions.ts b/src/lib/CommandPalette/authActions.ts index 9dbc9565..3dd51bf7 100644 --- a/src/lib/CommandPalette/authActions.ts +++ b/src/lib/CommandPalette/authActions.ts @@ -1,5 +1,6 @@ import { env } from "$env/dynamic/public"; import root from "$lib/Utility/root"; +import { track } from "$lib/analytics"; import localforage from "localforage"; import locale from "$stores/locale"; import { get } from "svelte/store"; @@ -18,6 +19,8 @@ export const authActions = ( preventDefault: true, tags: ["auth", "sign", "out", "user"], onClick: async () => { + track("Log Out", { source: "command-palette" }); + await localforage.removeItem("identity"); await localforage.removeItem("commit"); @@ -38,6 +41,8 @@ export const authActions = ( preventDefault: true, tags: ["auth", "sign", "in", "anilist"], onClick: async () => { + track("Log In", { source: "command-palette" }); + await localforage.setItem( "redirect", window.location.origin + diff --git a/src/lib/LandingHero.svelte b/src/lib/LandingHero.svelte index bec9d1f4..ed019274 100644 --- a/src/lib/LandingHero.svelte +++ b/src/lib/LandingHero.svelte @@ -2,6 +2,7 @@ import { env } from "$env/dynamic/public"; import localforage from "localforage"; import lenis from "$stores/lenis"; +import { track } from "$lib/analytics"; let heroSection = $state<HTMLElement>(); @@ -35,6 +36,8 @@ const scrollPastHero = () => { class="cta" href={`https://anilist.co/api/v2/oauth/authorize?client_id=${env.PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${env.PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`} onclick={async () => { + track('Log In', { source: 'landing' }); + await localforage.setItem( 'redirect', window.location.origin + window.location.pathname + window.location.search 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 </button> @@ -255,7 +263,10 @@ const increment = (anime: Media, progress: number) => { {#if media.length === 0} {$locale().lists.empty?.anime} - <button onclick={() => (animeLists = cleanCache(user, $identity))}> + <button + onclick={() => (animeLists = cleanCache(user, $identity))} + data-umami-event="Force Refresh Anime" + > {$locale().lists.actions?.forceRefresh} </button> {: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); + } }; </script> @@ -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 </button> 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: { diff --git a/src/lib/Notification/Notification.svelte b/src/lib/Notification/Notification.svelte index 1543f3b9..3c466b90 100644 --- a/src/lib/Notification/Notification.svelte +++ b/src/lib/Notification/Notification.svelte @@ -27,6 +27,7 @@ const remove = () => { id="notification-container" class={removed ? 'fade-out' : 'fade-in'} onclick={remove} + data-umami-event="Dismiss Notification" onkeydown={() => { return; }} diff --git a/src/lib/Settings/Categories/Debug.svelte b/src/lib/Settings/Categories/Debug.svelte index a2cb35c5..a18eea9d 100644 --- a/src/lib/Settings/Categories/Debug.svelte +++ b/src/lib/Settings/Categories/Debug.svelte @@ -25,11 +25,14 @@ import { get } from "svelte/store"; </SettingHint> <br /> -<button onclick={invalidateListCaches}>{$locale().debug.clearCaches}</button> +<button onclick={invalidateListCaches} data-umami-event="Clear List Caches" + >{$locale().debug.clearCaches}</button +> <Spacer /> <button + data-umami-event="Reset Settings" onclick={() => { settings.reset(); addNotification( @@ -48,6 +51,7 @@ import { get } from "svelte/store"; <Spacer /> <button + data-umami-event="Clear Local Database" onclick={async () => { await localforage.clear(); addNotification( diff --git a/src/lib/Settings/Categories/Display.svelte b/src/lib/Settings/Categories/Display.svelte index 80f21fde..b2acd270 100644 --- a/src/lib/Settings/Categories/Display.svelte +++ b/src/lib/Settings/Categories/Display.svelte @@ -7,8 +7,20 @@ import root from "$lib/Utility/root"; import locale from "$stores/locale"; import { requestNotifications } from "$lib/Utility/notifications"; import { getFingerprint } from "$lib/Utility/fingerprint"; +import { track } from "$lib/analytics"; + +const trackSetting = (key: string) => (event: Event) => + track("Change Setting", { + key, + value: (event.currentTarget as HTMLSelectElement).value, + }); const onHelperChange = () => { + track("Change Setting", { + key: "displayAoButa", + value: $settings.displayAoButa, + }); + const mai = document.getElementById("mai") as HTMLImageElement; if (!mai) return; @@ -182,7 +194,11 @@ const onHelperChange = () => { text={$locale().settings.display.categories.dataSaver} tooltipText={$locale().settings.display.tooltips.dataSaver} /> -<select bind:value={$settings.displayLanguage} class="no-shadow"> +<select + bind:value={$settings.displayLanguage} + class="no-shadow" + onchange={trackSetting('displayLanguage')} +> <option value="en"> {$locale({ locale: 'en' @@ -309,7 +325,7 @@ const onHelperChange = () => { <Spacer /> <b>{$locale().settings.display.categories.listSortFilterTitle}</b><br /> -<select bind:value={$settings.displayAnimeSort}> +<select bind:value={$settings.displayAnimeSort} onchange={trackSetting('displayAnimeSort')}> <option value="time_remaining" >{$locale().settings.display.categories.sortOptions?.timeRemaining}</option > @@ -375,7 +391,7 @@ const onHelperChange = () => { text={$locale().settings.display.categories.media.fields.scheduleFilterList} id="schedule-filter-list" /> -<select bind:value={$settings.displayTitleFormat}> +<select bind:value={$settings.displayTitleFormat} onchange={trackSetting('displayTitleFormat')}> <option value="english"> {$locale().settings.display.categories.media.fields.mediaTitleFormat.options.english} </option> @@ -399,7 +415,10 @@ const onHelperChange = () => { <Spacer /> -<select bind:value={$settings.displayOutboundLinksTo}> +<select + bind:value={$settings.displayOutboundLinksTo} + onchange={trackSetting('displayOutboundLinksTo')} +> <option value="anilist">AniList</option> <option value="livechartme">LiveChart.me</option> <option value="animeschedule">AnimeSchedule</option> diff --git a/src/lib/Settings/Categories/RSSFeeds.svelte b/src/lib/Settings/Categories/RSSFeeds.svelte index 08ba7292..eaf21345 100644 --- a/src/lib/Settings/Categories/RSSFeeds.svelte +++ b/src/lib/Settings/Categories/RSSFeeds.svelte @@ -11,6 +11,7 @@ export let user: { accessToken: string; refreshToken: string }; </script> <button + data-umami-event="Copy RSS Feed URL" onclick={() => { addNotification( options({ diff --git a/src/lib/Settings/Categories/SettingSync.svelte b/src/lib/Settings/Categories/SettingSync.svelte index dd19db49..e45fdfb9 100644 --- a/src/lib/Settings/Categories/SettingSync.svelte +++ b/src/lib/Settings/Categories/SettingSync.svelte @@ -13,6 +13,7 @@ import { get } from "svelte/store"; {#if !$settings.settingsSync} <button + data-umami-event="Pull Settings" onclick={() => { $settings.settingsSync = true; @@ -53,6 +54,7 @@ import { get } from "svelte/store"; </SettingHint> <Spacer /> <button + data-umami-event="Push Settings" onclick={() => { $settings.settingsSync = true; @@ -78,6 +80,7 @@ import { get } from "svelte/store"; </SettingHint> {:else} <button + data-umami-event="Disable Settings Sync" onclick={() => { $settings.settingsSync = false; @@ -91,6 +94,7 @@ import { get } from "svelte/store"; {$locale().settings.settingsSync.buttons.disable} </button> <button + data-umami-event="Delete Remote Settings" onclick={() => { fetch(root(`/api/configuration?id=${$identity.id}`), { method: 'DELETE' diff --git a/src/lib/Settings/SettingCheckboxToggle.svelte b/src/lib/Settings/SettingCheckboxToggle.svelte index 1f58520d..e45077f7 100644 --- a/src/lib/Settings/SettingCheckboxToggle.svelte +++ b/src/lib/Settings/SettingCheckboxToggle.svelte @@ -2,6 +2,7 @@ import Spacer from "$lib/Layout/Spacer.svelte"; import tooltip from "$lib/Tooltip/tooltip"; import settings, { type Settings } from "$stores/settings"; +import { track } from "$lib/analytics"; type BooleanSettingsKeys<T> = { [K in keyof T]: T[K] extends boolean ? K : never; @@ -52,13 +53,21 @@ const check = ( const checked = (e.target as HTMLInputElement).checked; if (setting) { - settings.setKey(setting, invert ? !checked : checked); + const value = invert ? !checked : checked; + + settings.setKey(setting, value); + track("Toggle Setting", { key: setting, value }); onChange(); } }; const flip = () => { - if (setting) $settings[setting] = !$settings[setting]; + if (setting) { + const value = !$settings[setting]; + + $settings[setting] = value; + track("Toggle Setting", { key: setting, value }); + } }; </script> diff --git a/src/lib/Settings/SettingToggle.svelte b/src/lib/Settings/SettingToggle.svelte index 0e77c9b0..a84a7a72 100644 --- a/src/lib/Settings/SettingToggle.svelte +++ b/src/lib/Settings/SettingToggle.svelte @@ -1,23 +1,25 @@ <script lang="ts"> import Spacer from "$lib/Layout/Spacer.svelte"; import settings, { type Settings } from "$stores/settings"; +import { track } from "$lib/analytics"; export let setting: keyof Settings; export let on = ""; export let off = ""; export let sectionBreak = false; export let disabled = false; + +const toggle = () => { + if (disabled) return; + + const value = !$settings[setting]; + + settings.setKey(setting, value); + track("Toggle Setting", { key: setting, value }); +}; </script> -<a - href={'#'} - onclick={() => - disabled - ? {} - : $settings[setting] - ? settings.setKey(setting, false) - : settings.setKey(setting, true)} -> +<a href={'#'} onclick={toggle}> {#if disabled} <strike> {$settings[setting] ? on : off} diff --git a/src/lib/Tools/ActivityHistory/Tool.svelte b/src/lib/Tools/ActivityHistory/Tool.svelte index 5e84db9e..df1613b9 100644 --- a/src/lib/Tools/ActivityHistory/Tool.svelte +++ b/src/lib/Tools/ActivityHistory/Tool.svelte @@ -94,7 +94,9 @@ const screenshot = async () => { <Spacer /> {/if} - <button onclick={screenshot}>Generate grid image</button> + <button onclick={screenshot} data-umami-event="Generate Activity History Image" + >Generate grid image</button + > </div> <Spacer /> diff --git a/src/lib/Tools/Tracker/Tool.svelte b/src/lib/Tools/Tracker/Tool.svelte index b495522a..3185e79b 100644 --- a/src/lib/Tools/Tracker/Tool.svelte +++ b/src/lib/Tools/Tracker/Tool.svelte @@ -88,8 +88,10 @@ const deleteEntry = async (id: string) => { placeholder={$locale().tools.tracker?.progressPlaceholder} bind:value={progress} /> - <button class="button-lined" onclick={() => addEntry(url, title, progress)} - >{$locale().common?.add}</button + <button + class="button-lined" + onclick={() => addEntry(url, title, progress)} + data-umami-event="Add Tracker Entry">{$locale().common?.add}</button > <Spacer /> @@ -125,16 +127,20 @@ const deleteEntry = async (id: string) => { <button class="button-square button-action" onclick={() => adjustEntry(entry.id, entry.progress - 1)} + data-umami-event="Decrement Tracker Progress" >- </button> <button class="button-square button-action" onclick={() => adjustEntry(entry.id, entry.progress + 1)} + data-umami-event="Increment Tracker Progress" > + </button> <span class="opaque">|</span> - <button onclick={() => deleteEntry(entry.id)}>{$locale().common?.remove}</button> + <button onclick={() => deleteEntry(entry.id)} data-umami-event="Delete Tracker Entry" + >{$locale().common?.remove}</button + > </span> </div> </li> diff --git a/src/lib/analytics.ts b/src/lib/analytics.ts new file mode 100644 index 00000000..014f72db --- /dev/null +++ b/src/lib/analytics.ts @@ -0,0 +1,11 @@ +/** + * Safe wrapper around umami's programmatic `track`. The analytics script is + * skipped on localhost, lazily loaded elsewhere, and routinely blocked by + * content blockers, so a bare `umami.track(...)` throws a ReferenceError that + * would take core actions (incrementing progress, syncing settings) down with + * it. Declarative `data-umami-event` attributes are handled by the script + * itself and never need this; reach for it only from event handlers. + */ +export const track = (event: string, data?: Record<string, unknown>): void => { + if (typeof umami !== "undefined") umami.track(event, data); +}; |