aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Announcement.svelte4
-rw-r--r--src/lib/CommandPalette/CommandPalette.svelte11
-rw-r--r--src/lib/CommandPalette/authActions.ts5
-rw-r--r--src/lib/LandingHero.svelte3
-rw-r--r--src/lib/List/Anime/CleanAnimeList.svelte13
-rw-r--r--src/lib/List/Manga/CleanMangaList.svelte11
-rw-r--r--src/lib/List/MediaRoulette.svelte4
-rw-r--r--src/lib/Notification/Notification.svelte1
-rw-r--r--src/lib/Settings/Categories/Debug.svelte6
-rw-r--r--src/lib/Settings/Categories/Display.svelte27
-rw-r--r--src/lib/Settings/Categories/RSSFeeds.svelte1
-rw-r--r--src/lib/Settings/Categories/SettingSync.svelte4
-rw-r--r--src/lib/Settings/SettingCheckboxToggle.svelte13
-rw-r--r--src/lib/Settings/SettingToggle.svelte20
-rw-r--r--src/lib/Tools/ActivityHistory/Tool.svelte4
-rw-r--r--src/lib/Tools/Tracker/Tool.svelte12
-rw-r--r--src/lib/analytics.ts11
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);
+};