aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Tools/Wrapped/Tool.svelte645
1 files changed, 426 insertions, 219 deletions
diff --git a/src/lib/Tools/Wrapped/Tool.svelte b/src/lib/Tools/Wrapped/Tool.svelte
index bcc33f68..f060f444 100644
--- a/src/lib/Tools/Wrapped/Tool.svelte
+++ b/src/lib/Tools/Wrapped/Tool.svelte
@@ -2,10 +2,17 @@
import userIdentity from '$stores/identity';
import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
import { onMount } from 'svelte';
- import { tops, wrapped, type TopMedia, SortOptions } from '$lib/Data/AniList/wrapped';
+ import {
+ tops,
+ wrapped,
+ type TopMedia,
+ SortOptions,
+ type Wrapped
+ } from '$lib/Data/AniList/wrapped';
import {
fullActivityHistory,
- activityHistory as getActivityHistory
+ activityHistory as getActivityHistory,
+ type ActivityHistoryEntry
} from '$lib/Data/AniList/activity';
import { Type, mediaListCollection, type Media } from '$lib/Data/AniList/media';
import anime from '$stores/anime';
@@ -74,6 +81,15 @@
let startDateFilter: Date | null = null;
let endDateFilter: Date | null = null;
let dateTicked = false;
+ let shouldFetchData = false;
+ let needsRefetch = false;
+ let dataFetched = false;
+ let fetchKey = 0;
+ let lastSelectedYear = selectedYear;
+ let lastUseFullActivityHistory = useFullActivityHistory;
+ let lastDisableLoopingActivityCounter = disableLoopingActivityCounter;
+ let lastStartDateFilter: Date | null = startDateFilter;
+ let lastEndDateFilter: Date | null = endDateFilter;
$: {
if (browser && mounted) {
@@ -108,6 +124,26 @@
history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
}
}
+
+ $: {
+ if (dataFetched) {
+ const yearChanged = selectedYear !== lastSelectedYear;
+ const fullActivityChanged = useFullActivityHistory !== lastUseFullActivityHistory;
+ const loopingChanged = disableLoopingActivityCounter !== lastDisableLoopingActivityCounter;
+ const startDateChanged = startDateFilter !== lastStartDateFilter;
+ const endDateChanged = endDateFilter !== lastEndDateFilter;
+
+ if (
+ yearChanged ||
+ fullActivityChanged ||
+ loopingChanged ||
+ startDateChanged ||
+ endDateChanged
+ )
+ needsRefetch = true;
+ }
+ }
+
$: {
includeMusic = includeMusic;
includeSpecials = includeSpecials;
@@ -285,6 +321,101 @@
await update().then(() => (mounted = true));
});
+ const triggerFetch = () => {
+ shouldFetchData = true;
+ needsRefetch = false;
+ dataFetched = true;
+ fetchKey += 1;
+ lastSelectedYear = selectedYear;
+ lastUseFullActivityHistory = useFullActivityHistory;
+ lastDisableLoopingActivityCounter = disableLoopingActivityCounter;
+ lastStartDateFilter = startDateFilter;
+ lastEndDateFilter = endDateFilter;
+ };
+
+ const createDummyMedia = (type: 'ANIME' | 'MANGA'): Media => ({
+ id: 0,
+ idMal: 0,
+ status: 'FINISHED',
+ type,
+ episodes: type === 'ANIME' ? 0 : 0,
+ chapters: type === 'MANGA' ? 0 : 0,
+ volumes: 0,
+ duration: 0,
+ format: type === 'ANIME' ? 'TV' : 'MANGA',
+ title: {
+ romaji: '...',
+ english: '...',
+ native: '...'
+ },
+ synonyms: [],
+ mediaListEntry: {
+ progress: 0,
+ progressVolumes: 0,
+ status: 'COMPLETED',
+ score: 0,
+ repeat: 0,
+ startedAt: {
+ year: 0,
+ month: 0,
+ day: 0
+ },
+ completedAt: {
+ year: 0,
+ month: 0,
+ day: 0
+ },
+ createdAt: 0,
+ updatedAt: 0,
+ customLists: new Map()
+ },
+ startDate: {
+ year: 0,
+ month: 0
+ },
+ endDate: {
+ year: 0,
+ month: 0
+ },
+ coverImage: {
+ extraLarge: 'https://s4.anilist.co/file/anilistcdn/staff/large/default.jpg',
+ medium: 'https://s4.anilist.co/file/anilistcdn/staff/large/default.jpg'
+ },
+ tags: [],
+ genres: [],
+ season: 'WINTER',
+ isAdult: false,
+ relations: {
+ edges: []
+ }
+ });
+
+ const dummyWrapped: Wrapped = {
+ statistics: {
+ anime: {
+ startYears: [],
+ genres: [],
+ tags: []
+ },
+ manga: {
+ startYears: [],
+ genres: [],
+ tags: []
+ }
+ },
+ activities: {
+ statusCount: 0,
+ messageCount: 0
+ },
+ avatar: {
+ large: 'https://s4.anilist.co/file/anilistcdn/user/avatar/large/3.jpg'
+ }
+ };
+
+ const dummyActivities: ActivityHistoryEntry[] = [];
+ const dummyAnimeList: Media[] = [createDummyMedia('ANIME')];
+ const dummyMangaList: Media[] = [createDummyMedia('MANGA')];
+
const update = async () => {
if ($userIdentity.id === -1) return;
@@ -614,235 +745,311 @@
{#if $userIdentity.id === -2 || user === undefined}
<LogInRestricted />
{:else if $userIdentity.id !== -1}
- {#await selectedYear !== currentYear || useFullActivityHistory || new Date().getMonth() <= 6 ? fullActivityHistory(user, $userIdentity, selectedYear, disableLoopingActivityCounter) : getActivityHistory($userIdentity)}
- <Message message="Loading activity history ..." />
-
- <Skeleton count={2} />
- {:then activities}
- {#await wrapped(user, $userIdentity, selectedYear, false, disableLoopingActivityCounter)}
- <Message message="Loading user data ..." />
-
- <Skeleton count={2} />
- {:then wrapped}
- <div id="list-container">
- <div class="card">
- <div
- id="wrapped"
- class:light-theme={lightMode}
- style={`width: ${width}px; flex-shrink: 0;`}
- class:transparent={transparency}
- >
- {#if !disableActivityHistory && activityHistoryPosition === 'TOP' && activities.length > 0 && selectedYear === currentYear}
- <ActivityHistory {user} {activities} year={selectedYear} {activityHistoryPosition} />
- {/if}
- <div class="categories-grid" style="padding-bottom: 0;">
- <Activity
- {wrapped}
- year={selectedYear}
- {activities}
- {useFullActivityHistory}
- {updateWidth}
- />
- <Anime animeList={calculatedAnimeList} {minutesWatched} {episodes} />
- <Manga mangaList={calculatedMangaList} {chapters} />
+ <div id="list-container">
+ {#if shouldFetchData}
+ {#key fetchKey}
+ {#await selectedYear !== currentYear || useFullActivityHistory || new Date().getMonth() <= 6 ? fullActivityHistory(user, $userIdentity, selectedYear, disableLoopingActivityCounter) : getActivityHistory($userIdentity)}
+ <Message message="Loading activity history ..." />
+
+ <Skeleton count={2} />
+ {:then activities}
+ {#await wrapped(user, $userIdentity, selectedYear, false, disableLoopingActivityCounter)}
+ <Message message="Loading user data ..." />
+
+ <Skeleton count={2} />
+ {:then wrapped}
+ <div class="card">
+ <div
+ id="wrapped"
+ class:light-theme={lightMode}
+ style={`width: ${width}px; flex-shrink: 0;`}
+ class:transparent={transparency}
+ >
+ {#if !disableActivityHistory && activityHistoryPosition === 'TOP' && activities.length > 0 && selectedYear === currentYear}
+ <ActivityHistory
+ {user}
+ {activities}
+ year={selectedYear}
+ {activityHistoryPosition}
+ />
+ {/if}
+ <div class="categories-grid" style="padding-bottom: 0;">
+ <Activity
+ {wrapped}
+ year={selectedYear}
+ {activities}
+ {useFullActivityHistory}
+ {updateWidth}
+ />
+ <Anime animeList={calculatedAnimeList} {minutesWatched} {episodes} />
+ <Manga mangaList={calculatedMangaList} {chapters} />
+ </div>
+ {#if !disableActivityHistory && activityHistoryPosition === 'BELOW_TOP' && activities.length > 0 && selectedYear === currentYear}
+ <ActivityHistory
+ {user}
+ {activities}
+ year={selectedYear}
+ {activityHistoryPosition}
+ />
+ {/if}
+ <MediaPanel
+ {animeList}
+ {mangaList}
+ {highestRatedMediaPercentage}
+ {highestRatedCount}
+ {updateWidth}
+ {wrapped}
+ {animeMostTitle}
+ {mangaMostTitle}
+ />
+ {#if topMedia && topGenresTags && ((topMedia.topGenreMedia && topMedia.genres.length > 0) || (topMedia.topTagMedia && topMedia.tags.length > 0))}
+ <MediaExtras
+ {topMedia}
+ {genreTagTitle}
+ {highestRatedGenreTagPercentage}
+ {updateWidth}
+ />
+ {/if}
+ {#if !disableActivityHistory && activityHistoryPosition === 'ORIGINAL' && activities.length > 0 && selectedYear === currentYear}
+ <ActivityHistory
+ {user}
+ {activities}
+ year={selectedYear}
+ {activityHistoryPosition}
+ />
+ {/if}
+ {#if watermark}
+ <Watermark />
+ {/if}
+ </div>
</div>
- {#if !disableActivityHistory && activityHistoryPosition === 'BELOW_TOP' && activities.length > 0 && selectedYear === currentYear}
- <ActivityHistory {user} {activities} year={selectedYear} {activityHistoryPosition} />
+ {:catch}
+ <Error type="User" card list={false} />
+ {/await}
+ {:catch}
+ <Error
+ card
+ type={`${useFullActivityHistory ? 'Full-year activity' : 'Activity'} history`}
+ loginSessionError={!useFullActivityHistory}
+ list={false}
+ >
+ {#if useFullActivityHistory}
+ <p>
+ With <b>many</b> activities, it may take multiple attempts to obtain all of your activity
+ history from AniList. If this occurs, wait one minute and try again to continue populating
+ your local activity history database.
+ </p>
{/if}
- <MediaPanel
- {animeList}
- {mangaList}
- {highestRatedMediaPercentage}
- {highestRatedCount}
+ </Error>
+ {/await}
+ {/key}
+ {:else}
+ <div class="card">
+ <div
+ id="wrapped"
+ class:light-theme={lightMode}
+ style={`width: ${width}px; flex-shrink: 0;`}
+ class:transparent={transparency}
+ >
+ <div class="categories-grid" style="padding-bottom: 0;">
+ <Activity
+ wrapped={dummyWrapped}
+ year={selectedYear}
+ activities={dummyActivities}
+ {useFullActivityHistory}
{updateWidth}
- {wrapped}
- {animeMostTitle}
- {mangaMostTitle}
/>
- {#if topMedia && topGenresTags && ((topMedia.topGenreMedia && topMedia.genres.length > 0) || (topMedia.topTagMedia && topMedia.tags.length > 0))}
- <MediaExtras
- {topMedia}
- {genreTagTitle}
- {highestRatedGenreTagPercentage}
- {updateWidth}
- />
- {/if}
- {#if !disableActivityHistory && activityHistoryPosition === 'ORIGINAL' && activities.length > 0 && selectedYear === currentYear}
- <ActivityHistory {user} {activities} year={selectedYear} {activityHistoryPosition} />
- {/if}
- {#if watermark}
- <Watermark />
- {/if}
- </div>
- </div>
- <div class="list">
- <div class:card={generated}>
- <div id="wrapped-final" />
-
- {#if generated}
- <p />
-
- <blockquote style="margin: 0 0 0 1.5rem;">
- Click on the image to download, or right click and select "Save Image As...".
- </blockquote>
- {/if}
+ <Anime animeList={dummyAnimeList} minutesWatched={0} episodes={0} />
+ <Manga mangaList={dummyMangaList} chapters={0} />
</div>
- {#if generated}
- <p />
- {/if}
-
- <div id="options" class="card">
- <button on:click={screenshot} data-umami-event="Generate Wrapped">
- Generate image
- </button>
-
- <details class="no-shadow" open>
- <summary>Display</summary>
-
- <input type="checkbox" bind:checked={watermark} /> Show watermark<br />
- <input type="checkbox" bind:checked={transparency} /> Enable background transparency<br
- />
- <input type="checkbox" bind:checked={lightMode} />
- Enable light mode<br />
- <input type="checkbox" bind:checked={topGenresTags} />
- Show top genres and tags<br />
- <input
- type="checkbox"
- bind:checked={disableActivityHistory}
- disabled={selectedYear !== currentYear}
- />
- Hide activity history<br />
- <input type="checkbox" bind:checked={highestRatedMediaPercentage} /> Show highest
- rated media percentages<br />
- <input type="checkbox" bind:checked={highestRatedGenreTagPercentage} /> Show highest
- rated genre and tag percentages<br />
- <input type="checkbox" bind:checked={includeOngoingMediaFromPreviousYears} /> Show
- ongoing media from previous years<br />
- <select bind:value={activityHistoryPosition}>
- <option value="TOP">Above Top Row</option>
- <option value="BELOW_TOP">Below Top Row</option>
- <option value="ORIGINAL">Bottom</option>
- </select>
- Activity history position<br />
- <select bind:value={highestRatedCount}>
- {#each [3, 4, 5, 6, 7, 8, 9, 10] as count}
- <option value={count}>{count}</option>
- {/each}
- </select>
- Highest rated media count<br />
- <select bind:value={genreTagCount}>
- {#each [3, 4, 5, 6, 7, 8, 9, 10] as count}
- <option value={count}>{count}</option>
- {/each}
- </select>
- Highest genre and tag count<br />
- <button on:click={updateWidth}>Find best fit</button>
- <button on:click={() => (width -= 25)}>-25px</button>
- <button on:click={() => (width += 25)}>+25px</button>
- Width adjustment<br />
- </details>
-
- <details class="no-shadow" open>
- <summary>Calculation</summary>
-
- <input type="checkbox" bind:checked={useFullActivityHistory} />
- Enable full-year activity<button class="smaller-button" on:click={pruneFullYear}
- >Refresh data</button
- >
- <br />
- <select bind:value={selectedYear}>
- {#each Array.from({ length: currentYear - 2012 }) as _, i}
- <option value={currentYear - i}>
- {currentYear - i}
- </option>
- {/each}
- </select>
- Calculate for year<br />
- <input
- type="date"
- bind:value={startDateFilter}
- placeholder="Start date"
- on:change={() => {
- dateTicked = true;
-
- update();
+ <MediaPanel
+ animeList={dummyAnimeList}
+ mangaList={dummyMangaList}
+ {highestRatedMediaPercentage}
+ {highestRatedCount}
+ {updateWidth}
+ wrapped={dummyWrapped}
+ {animeMostTitle}
+ {mangaMostTitle}
+ />
+
+ <div class="categories-grid" style="padding-top: 0;">
+ <div class="category-grid pure-category">
+ <a
+ href={'#'}
+ on:click={(e) => {
+ e.preventDefault();
+ triggerFetch();
}}
- />
- Start date filter<br />
- <input
- type="date"
- bind:value={endDateFilter}
- placeholder="End date"
- on:change={() => {
- dateTicked = true;
-
- update();
- }}
- />
- End date filter<br />
- <select bind:value={mediaSort}>
- <option value={SortOptions.SCORE}>Score</option>
- <option value={SortOptions.MINUTES_WATCHED}>Minutes Watched/Read</option>
- </select>
- Anime and manga sort<br />
- <select bind:value={genreTagsSort}>
- <option value={SortOptions.SCORE}>Score</option>
- <option value={SortOptions.MINUTES_WATCHED}>Minutes Watched/Read</option>
- <option value={SortOptions.COUNT}>Count</option>
- </select>
- Genre and tag sort<br />
- <input type="checkbox" bind:checked={includeMusic} /> Include music<br />
- <input type="checkbox" bind:checked={includeRepeats} /> Include rewatches & rereads<br
- />
- <input type="checkbox" bind:checked={includeSpecials} /> Include specials<br />
- <input type="checkbox" bind:checked={includeOVAs} /> Include OVAs<br />
- <input type="checkbox" bind:checked={includeMovies} /> Include movies<br />
- <input type="checkbox" bind:checked={excludeUnratedUnwatched} /> Excluded unrated &
- unwatched<br />
- <input
- type="text"
- bind:value={excludedKeywordsInput}
- on:keypress={(e) => {
- e.key === 'Enter' && submitExcludedKeywords();
- }}
- />
- Excluded keywords
- <button on:click={submitExcludedKeywords} title="Or click your Enter key" use:tooltip
- >Submit</button
+ id="watermark"
+ data-umami-event="Load Wrapped Data"
>
- <br />
- <SettingHint>Comma separated list (e.g., "My Hero, Kaguya")</SettingHint>
- </details>
-
- <details class="no-shadow" open>
- <summary>Advanced</summary>
-
- <input type="checkbox" bind:checked={disableLoopingActivityCounter} />
- Disable detailed activity information
- </details>
+ Click load data!
+ </a>
+ </div>
</div>
</div>
</div>
- {:catch}
- <Error type="User" card list={false} />
- {/await}
- {:catch}
- <Error
- card
- type={`${useFullActivityHistory ? 'Full-year activity' : 'Activity'} history`}
- loginSessionError={!useFullActivityHistory}
- list={false}
- >
- {#if useFullActivityHistory}
- <p>
- With <b>many</b> activities, it may take multiple attempts to obtain all of your activity history
- from AniList. If this occurs, wait one minute and try again to continue populating your local
- activity history database.
- </p>
+ {/if}
+ <div class="list">
+ <div class:card={generated}>
+ <div id="wrapped-final" />
+
+ {#if generated}
+ <p />
+
+ <blockquote style="margin: 0 0 0 1.5rem;">
+ Click on the image to download, or right click and select "Save Image As...".
+ </blockquote>
+ {/if}
+ </div>
+
+ {#if generated}
+ <p />
{/if}
- </Error>
- {/await}
+
+ <div id="options" class="card">
+ <button on:click={screenshot} data-umami-event="Generate Wrapped"> Generate image </button>
+ {#if !shouldFetchData}
+ <button on:click={triggerFetch} data-umami-event="Load Wrapped Data"> Load data </button>
+ {:else if needsRefetch}
+ <button on:click={triggerFetch} data-umami-event="Refetch Wrapped Data">
+ Reload data
+ </button>
+ {/if}
+
+ <details class="no-shadow" open>
+ <summary>Display</summary>
+
+ <input type="checkbox" bind:checked={watermark} /> Show watermark<br />
+ <input type="checkbox" bind:checked={transparency} /> Enable background transparency<br />
+ <input type="checkbox" bind:checked={lightMode} />
+ Enable light mode<br />
+ <input type="checkbox" bind:checked={topGenresTags} />
+ Show top genres and tags<br />
+ <input
+ type="checkbox"
+ bind:checked={disableActivityHistory}
+ disabled={selectedYear !== currentYear}
+ />
+ Hide activity history<br />
+ <input type="checkbox" bind:checked={highestRatedMediaPercentage} /> Show highest rated
+ media percentages<br />
+ <input type="checkbox" bind:checked={highestRatedGenreTagPercentage} /> Show highest rated
+ genre and tag percentages<br />
+ <input type="checkbox" bind:checked={includeOngoingMediaFromPreviousYears} /> Show ongoing
+ media from previous years<br />
+ <select bind:value={activityHistoryPosition}>
+ <option value="TOP">Above Top Row</option>
+ <option value="BELOW_TOP">Below Top Row</option>
+ <option value="ORIGINAL">Bottom</option>
+ </select>
+ Activity history position<br />
+ <select bind:value={highestRatedCount}>
+ {#each [3, 4, 5, 6, 7, 8, 9, 10] as count}
+ <option value={count}>{count}</option>
+ {/each}
+ </select>
+ Highest rated media count<br />
+ <select bind:value={genreTagCount}>
+ {#each [3, 4, 5, 6, 7, 8, 9, 10] as count}
+ <option value={count}>{count}</option>
+ {/each}
+ </select>
+ Highest genre and tag count<br />
+ <button on:click={updateWidth}>Find best fit</button>
+ <button on:click={() => (width -= 25)}>-25px</button>
+ <button on:click={() => (width += 25)}>+25px</button>
+ Width adjustment<br />
+ </details>
+
+ <details class="no-shadow" open>
+ <summary>Calculation</summary>
+
+ <input type="checkbox" bind:checked={useFullActivityHistory} disabled={needsRefetch} />
+ Enable full-year activity<button class="smaller-button" on:click={pruneFullYear}
+ >Refresh data</button
+ >
+ <br />
+ <select bind:value={selectedYear} disabled={needsRefetch}>
+ {#each Array.from({ length: currentYear - 2012 }) as _, i}
+ <option value={currentYear - i}>
+ {currentYear - i}
+ </option>
+ {/each}
+ </select>
+ Calculate for year<br />
+ <input
+ type="date"
+ bind:value={startDateFilter}
+ placeholder="Start date"
+ disabled={needsRefetch}
+ on:change={() => {
+ dateTicked = true;
+
+ update();
+ }}
+ />
+ Start date filter<br />
+ <input
+ type="date"
+ bind:value={endDateFilter}
+ placeholder="End date"
+ disabled={needsRefetch}
+ on:change={() => {
+ dateTicked = true;
+
+ update();
+ }}
+ />
+ End date filter<br />
+ <select bind:value={mediaSort}>
+ <option value={SortOptions.SCORE}>Score</option>
+ <option value={SortOptions.MINUTES_WATCHED}>Minutes Watched/Read</option>
+ </select>
+ Anime and manga sort<br />
+ <select bind:value={genreTagsSort}>
+ <option value={SortOptions.SCORE}>Score</option>
+ <option value={SortOptions.MINUTES_WATCHED}>Minutes Watched/Read</option>
+ <option value={SortOptions.COUNT}>Count</option>
+ </select>
+ Genre and tag sort<br />
+ <input type="checkbox" bind:checked={includeMusic} /> Include music<br />
+ <input type="checkbox" bind:checked={includeRepeats} /> Include rewatches & rereads<br />
+ <input type="checkbox" bind:checked={includeSpecials} /> Include specials<br />
+ <input type="checkbox" bind:checked={includeOVAs} /> Include OVAs<br />
+ <input type="checkbox" bind:checked={includeMovies} /> Include movies<br />
+ <input type="checkbox" bind:checked={excludeUnratedUnwatched} /> Excluded unrated &
+ unwatched<br />
+ <input
+ type="text"
+ bind:value={excludedKeywordsInput}
+ on:keypress={(e) => {
+ e.key === 'Enter' && submitExcludedKeywords();
+ }}
+ />
+ Excluded keywords
+ <button on:click={submitExcludedKeywords} title="Or click your Enter key" use:tooltip>
+ Submit
+ </button>
+ <br />
+ <SettingHint>Comma separated list (e.g., "My Hero, Kaguya")</SettingHint>
+ </details>
+
+ <details class="no-shadow" open>
+ <summary>Advanced</summary>
+
+ <input
+ type="checkbox"
+ bind:checked={disableLoopingActivityCounter}
+ disabled={needsRefetch}
+ />
+ Disable detailed activity information
+ </details>
+ </div>
+ </div>
+ </div>
{:else}
<Message message="Loading user ..." />