aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Tools/Wrapped
diff options
context:
space:
mode:
authorFuwn <[email protected]>2024-10-09 00:41:20 -0700
committerFuwn <[email protected]>2024-10-09 00:41:43 -0700
commit998b63a35256ac985a5a2714dd1ca451af4dfd8a (patch)
tree50796121a9d5ab0330fdc5d7e098bda2860d9726 /src/lib/Tools/Wrapped
parentfeat(graphql): add badgeCount field (diff)
downloaddue.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.tar.xz
due.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.zip
chore(prettier): use spaces instead of tabs
Diffstat (limited to 'src/lib/Tools/Wrapped')
-rw-r--r--src/lib/Tools/Wrapped/ActivityHistory.svelte28
-rw-r--r--src/lib/Tools/Wrapped/Media.svelte184
-rw-r--r--src/lib/Tools/Wrapped/MediaExtras.svelte136
-rw-r--r--src/lib/Tools/Wrapped/Tool.svelte1530
-rw-r--r--src/lib/Tools/Wrapped/Top/Activity.svelte70
-rw-r--r--src/lib/Tools/Wrapped/Top/Anime.svelte28
-rw-r--r--src/lib/Tools/Wrapped/Top/Manga.svelte32
-rw-r--r--src/lib/Tools/Wrapped/Watermark.svelte6
-rw-r--r--src/lib/Tools/Wrapped/wrapped.css84
9 files changed, 1049 insertions, 1049 deletions
diff --git a/src/lib/Tools/Wrapped/ActivityHistory.svelte b/src/lib/Tools/Wrapped/ActivityHistory.svelte
index 194f5951..3da401d4 100644
--- a/src/lib/Tools/Wrapped/ActivityHistory.svelte
+++ b/src/lib/Tools/Wrapped/ActivityHistory.svelte
@@ -1,21 +1,21 @@
<script lang="ts">
- import type { ActivityHistoryEntry } from '$lib/Data/AniList/activity';
- import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
- import ActivityHistoryGrid from '../ActivityHistory/Grid.svelte';
+ import type { ActivityHistoryEntry } from '$lib/Data/AniList/activity';
+ import type { AniListAuthorisation } from '$lib/Data/AniList/identity';
+ import ActivityHistoryGrid from '../ActivityHistory/Grid.svelte';
- export let user: AniListAuthorisation;
- export let activities: ActivityHistoryEntry[];
- export let year: number;
- export let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL';
+ export let user: AniListAuthorisation;
+ export let activities: ActivityHistoryEntry[];
+ export let year: number;
+ export let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL';
</script>
<div
- class="categories-grid"
- style={`padding-${activityHistoryPosition === 'ORIGINAL' ? 'top' : 'bottom'}: 0;`}
+ class="categories-grid"
+ style={`padding-${activityHistoryPosition === 'ORIGINAL' ? 'top' : 'bottom'}: 0;`}
>
- <div class="category-grid bottom-category pure-category category">
- <div id="activity-history">
- <ActivityHistoryGrid {user} activityData={activities} currentYear={year} />
- </div>
- </div>
+ <div class="category-grid bottom-category pure-category category">
+ <div id="activity-history">
+ <ActivityHistoryGrid {user} activityData={activities} currentYear={year} />
+ </div>
+ </div>
</div>
diff --git a/src/lib/Tools/Wrapped/Media.svelte b/src/lib/Tools/Wrapped/Media.svelte
index f5184bc4..ea8a989b 100644
--- a/src/lib/Tools/Wrapped/Media.svelte
+++ b/src/lib/Tools/Wrapped/Media.svelte
@@ -1,98 +1,98 @@
<script lang="ts">
- import type { Media } from '$lib/Data/AniList/media';
- import type { Wrapped } from '$lib/Data/AniList/wrapped';
- import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
- import proxy from '$lib/Utility/proxy';
+ import type { Media } from '$lib/Data/AniList/media';
+ import type { Wrapped } from '$lib/Data/AniList/wrapped';
+ import MediaTitleDisplay from '$lib/List/MediaTitleDisplay.svelte';
+ import proxy from '$lib/Utility/proxy';
- export let animeList: Media[] | undefined;
- export let mangaList: Media[] | undefined;
- export let wrapped: Wrapped;
- export let updateWidth: () => void;
- export let highestRatedMediaPercentage: boolean;
- export let highestRatedCount: number;
- export let animeMostTitle: string;
- export let mangaMostTitle: string;
+ export let animeList: Media[] | undefined;
+ export let mangaList: Media[] | undefined;
+ export let wrapped: Wrapped;
+ export let updateWidth: () => void;
+ export let highestRatedMediaPercentage: boolean;
+ export let highestRatedCount: number;
+ export let animeMostTitle: string;
+ export let mangaMostTitle: string;
</script>
{#if animeList !== undefined || mangaList !== undefined}
- <div class="categories-grid">
- <div class="category-grid pure-category category middle-category">
- <div class="grid-item image-grid">
- <a
- href={animeList && animeList[0] ? `https://anilist.co/anime/${animeList[0].id}` : '#'}
- target="_blank"
- >
- <img
- src={proxy(
- animeList && animeList[0] ? animeList[0].coverImage.extraLarge : wrapped.avatar.large
- )}
- alt="Highest Rated Anime Cover"
- class="cover-image"
- on:load={updateWidth}
- />
- </a>
- <div>
- <b>{animeMostTitle} Anime</b>
- <ol>
- {#if animeList !== undefined && animeList.length !== 0}
- {#each animeList?.slice(0, highestRatedCount) as anime}
- <li>
- <a href={`https://anilist.co/anime/${anime.id}`} target="_blank">
- <MediaTitleDisplay title={anime.title} />
- </a>{highestRatedMediaPercentage &&
- anime.mediaListEntry &&
- anime.mediaListEntry?.score > 0
- ? `: ${anime.mediaListEntry?.score}%`
- : ''}
- </li>
- {/each}
- {:else}
- <li>
- <p class="opaque">(⌣_⌣”)</p>
- </li>
- {/if}
- </ol>
- </div>
- </div>
- </div>
- <div class="category-grid pure-category category middle-category">
- <div class="grid-item image-grid">
- <a
- href={mangaList && mangaList[0] ? `https://anilist.co/manga/${mangaList[0].id}` : '#'}
- target="_blank"
- >
- <img
- src={proxy(
- mangaList && mangaList[0] ? mangaList[0].coverImage.extraLarge : wrapped.avatar.large
- )}
- alt="Highest Rated Manga Cover"
- class="cover-image"
- on:load={updateWidth}
- />
- </a>
- <div>
- <b>{mangaMostTitle} Manga</b>
- <ol>
- {#if mangaList !== undefined && mangaList.length !== 0}
- {#each mangaList?.slice(0, highestRatedCount) as manga}
- <li>
- <a href={`https://anilist.co/manga/${manga.id}`} target="_blank">
- <MediaTitleDisplay title={manga.title} />
- </a>{highestRatedMediaPercentage &&
- manga.mediaListEntry &&
- manga.mediaListEntry?.score > 0
- ? `: ${manga.mediaListEntry?.score}%`
- : ''}
- </li>
- {/each}
- {:else}
- <li>
- <p class="opaque">(⌣_⌣”)</p>
- </li>
- {/if}
- </ol>
- </div>
- </div>
- </div>
- </div>
+ <div class="categories-grid">
+ <div class="category-grid pure-category category middle-category">
+ <div class="grid-item image-grid">
+ <a
+ href={animeList && animeList[0] ? `https://anilist.co/anime/${animeList[0].id}` : '#'}
+ target="_blank"
+ >
+ <img
+ src={proxy(
+ animeList && animeList[0] ? animeList[0].coverImage.extraLarge : wrapped.avatar.large
+ )}
+ alt="Highest Rated Anime Cover"
+ class="cover-image"
+ on:load={updateWidth}
+ />
+ </a>
+ <div>
+ <b>{animeMostTitle} Anime</b>
+ <ol>
+ {#if animeList !== undefined && animeList.length !== 0}
+ {#each animeList?.slice(0, highestRatedCount) as anime}
+ <li>
+ <a href={`https://anilist.co/anime/${anime.id}`} target="_blank">
+ <MediaTitleDisplay title={anime.title} />
+ </a>{highestRatedMediaPercentage &&
+ anime.mediaListEntry &&
+ anime.mediaListEntry?.score > 0
+ ? `: ${anime.mediaListEntry?.score}%`
+ : ''}
+ </li>
+ {/each}
+ {:else}
+ <li>
+ <p class="opaque">(⌣_⌣”)</p>
+ </li>
+ {/if}
+ </ol>
+ </div>
+ </div>
+ </div>
+ <div class="category-grid pure-category category middle-category">
+ <div class="grid-item image-grid">
+ <a
+ href={mangaList && mangaList[0] ? `https://anilist.co/manga/${mangaList[0].id}` : '#'}
+ target="_blank"
+ >
+ <img
+ src={proxy(
+ mangaList && mangaList[0] ? mangaList[0].coverImage.extraLarge : wrapped.avatar.large
+ )}
+ alt="Highest Rated Manga Cover"
+ class="cover-image"
+ on:load={updateWidth}
+ />
+ </a>
+ <div>
+ <b>{mangaMostTitle} Manga</b>
+ <ol>
+ {#if mangaList !== undefined && mangaList.length !== 0}
+ {#each mangaList?.slice(0, highestRatedCount) as manga}
+ <li>
+ <a href={`https://anilist.co/manga/${manga.id}`} target="_blank">
+ <MediaTitleDisplay title={manga.title} />
+ </a>{highestRatedMediaPercentage &&
+ manga.mediaListEntry &&
+ manga.mediaListEntry?.score > 0
+ ? `: ${manga.mediaListEntry?.score}%`
+ : ''}
+ </li>
+ {/each}
+ {:else}
+ <li>
+ <p class="opaque">(⌣_⌣”)</p>
+ </li>
+ {/if}
+ </ol>
+ </div>
+ </div>
+ </div>
+ </div>
{/if}
diff --git a/src/lib/Tools/Wrapped/MediaExtras.svelte b/src/lib/Tools/Wrapped/MediaExtras.svelte
index 9ef8cb65..9e755ea5 100644
--- a/src/lib/Tools/Wrapped/MediaExtras.svelte
+++ b/src/lib/Tools/Wrapped/MediaExtras.svelte
@@ -1,74 +1,74 @@
<script lang="ts">
- import type { TopMedia } from '$lib/Data/AniList/wrapped';
- import proxy from '$lib/Utility/proxy';
+ import type { TopMedia } from '$lib/Data/AniList/wrapped';
+ import proxy from '$lib/Utility/proxy';
- export let topMedia: TopMedia;
- export let updateWidth: () => void;
- export let highestRatedGenreTagPercentage: boolean;
- export let genreTagTitle: string;
+ export let topMedia: TopMedia;
+ export let updateWidth: () => void;
+ export let highestRatedGenreTagPercentage: boolean;
+ export let genreTagTitle: string;
</script>
<div class="categories-grid" style="padding-top: 0;">
- {#if topMedia.topGenreMedia && topMedia.genres.length > 0}
- <div class="category-grid pure-category category">
- <div class="grid-item image-grid">
- <a
- href={`https://anilist.co/${topMedia.topGenreMedia.type.toLowerCase()}/${
- topMedia.topGenreMedia.id
- }`}
- target="_blank"
- >
- <img
- src={proxy(topMedia.topGenreMedia.coverImage.extraLarge)}
- alt="Highest Rated Genre Cover"
- class="cover-image"
- on:load={updateWidth}
- />
- </a>
- <div>
- <b>{genreTagTitle} Genres</b>
- <ol>
- {#each topMedia.genres as genre}
- <li>
- <a href={`https://anilist.co/search/anime?genres=${genre.genre}`} target="_blank">
- {genre.genre}{highestRatedGenreTagPercentage ? `: ${genre.averageScore}%` : ''}
- </a>
- </li>
- {/each}
- </ol>
- </div>
- </div>
- </div>
- {/if}
- {#if topMedia.topTagMedia && topMedia.tags.length > 0}
- <div class="category-grid pure-category category">
- <div class="grid-item image-grid">
- <a
- href={`https://anilist.co/${topMedia.topTagMedia.type.toLowerCase()}/${
- topMedia.topTagMedia.id
- }`}
- target="_blank"
- >
- <img
- src={proxy(topMedia.topTagMedia.coverImage.extraLarge)}
- alt="Highest Rated Tag Cover"
- class="cover-image"
- on:load={updateWidth}
- />
- </a>
- <div>
- <b>{genreTagTitle} Tags</b>
- <ol>
- {#each topMedia.tags as tag}
- <li>
- <a href={`https://anilist.co/search/anime?genres=${tag.tag}`} target="_blank">
- {tag.tag}{highestRatedGenreTagPercentage ? `: ${tag.averageScore}%` : ''}
- </a>
- </li>
- {/each}
- </ol>
- </div>
- </div>
- </div>
- {/if}
+ {#if topMedia.topGenreMedia && topMedia.genres.length > 0}
+ <div class="category-grid pure-category category">
+ <div class="grid-item image-grid">
+ <a
+ href={`https://anilist.co/${topMedia.topGenreMedia.type.toLowerCase()}/${
+ topMedia.topGenreMedia.id
+ }`}
+ target="_blank"
+ >
+ <img
+ src={proxy(topMedia.topGenreMedia.coverImage.extraLarge)}
+ alt="Highest Rated Genre Cover"
+ class="cover-image"
+ on:load={updateWidth}
+ />
+ </a>
+ <div>
+ <b>{genreTagTitle} Genres</b>
+ <ol>
+ {#each topMedia.genres as genre}
+ <li>
+ <a href={`https://anilist.co/search/anime?genres=${genre.genre}`} target="_blank">
+ {genre.genre}{highestRatedGenreTagPercentage ? `: ${genre.averageScore}%` : ''}
+ </a>
+ </li>
+ {/each}
+ </ol>
+ </div>
+ </div>
+ </div>
+ {/if}
+ {#if topMedia.topTagMedia && topMedia.tags.length > 0}
+ <div class="category-grid pure-category category">
+ <div class="grid-item image-grid">
+ <a
+ href={`https://anilist.co/${topMedia.topTagMedia.type.toLowerCase()}/${
+ topMedia.topTagMedia.id
+ }`}
+ target="_blank"
+ >
+ <img
+ src={proxy(topMedia.topTagMedia.coverImage.extraLarge)}
+ alt="Highest Rated Tag Cover"
+ class="cover-image"
+ on:load={updateWidth}
+ />
+ </a>
+ <div>
+ <b>{genreTagTitle} Tags</b>
+ <ol>
+ {#each topMedia.tags as tag}
+ <li>
+ <a href={`https://anilist.co/search/anime?genres=${tag.tag}`} target="_blank">
+ {tag.tag}{highestRatedGenreTagPercentage ? `: ${tag.averageScore}%` : ''}
+ </a>
+ </li>
+ {/each}
+ </ol>
+ </div>
+ </div>
+ </div>
+ {/if}
</div>
diff --git a/src/lib/Tools/Wrapped/Tool.svelte b/src/lib/Tools/Wrapped/Tool.svelte
index 81a60016..1484ab5c 100644
--- a/src/lib/Tools/Wrapped/Tool.svelte
+++ b/src/lib/Tools/Wrapped/Tool.svelte
@@ -1,776 +1,776 @@
<script lang="ts">
- 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 {
- fullActivityHistory,
- activityHistory as getActivityHistory
- } from '$lib/Data/AniList/activity';
- import { Type, mediaListCollection, type Media } from '$lib/Data/AniList/media';
- import anime from '$stores/anime';
- import lastPruneTimes from '$stores/lastPruneTimes';
- import manga from '$stores/manga';
- import Error from '$lib/Error/RateLimited.svelte';
- import { domToBlob } from 'modern-screenshot';
- import { browser } from '$app/environment';
- import { page } from '$app/stores';
- import { clearAllParameters } from '../../Utility/parameters';
- import SettingHint from '$lib/Settings/SettingHint.svelte';
- import { database } from '$lib/Database/IDB/activities';
- import Activity from './Top/Activity.svelte';
- import Anime from './Top/Anime.svelte';
- import Manga from './Top/Manga.svelte';
- import ActivityHistory from './ActivityHistory.svelte';
- import MediaExtras from './MediaExtras.svelte';
- import MediaPanel from './Media.svelte';
- import Watermark from './Watermark.svelte';
- import Skeleton from '$lib/Loading/Skeleton.svelte';
- import Message from '$lib/Loading/Message.svelte';
- import tooltip from '$lib/Tooltip/tooltip';
- import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
-
- export let user: AniListAuthorisation;
-
- const currentYear = new Date(Date.now()).getFullYear();
- let selectedYear = new Date(Date.now()).getFullYear();
- let episodes = 0;
- let chapters = 0;
- let minutesWatched = 0;
- let animeList: Media[] | undefined = undefined;
- let mangaList: Media[] | undefined = undefined;
- let calculatedAnimeList: Media[] | undefined = undefined;
- let calculatedMangaList: Media[] | undefined = undefined;
- let originalAnimeList: Media[] | undefined = undefined;
- let originalMangaList: Media[] | undefined = undefined;
- let transparency = false;
- let lightTheme = true;
- let watermark = false;
- let includeMusic = false;
- let includeSpecials = true;
- let includeRepeats = false;
- let width = 1920;
- let lightMode = false;
- let highestRatedCount = 5;
- let genreTagCount = 5;
- let mounted = false;
- let generated = false;
- let disableActivityHistory = true;
- let excludedKeywordsInput = '';
- let excludedKeywords: string[] = [];
- let useFullActivityHistory = false;
- let topGenresTags = true;
- let topMedia: TopMedia;
- let highestRatedMediaPercentage = true;
- let highestRatedGenreTagPercentage = true;
- let genreTagsSort = SortOptions.SCORE;
- let mediaSort = SortOptions.SCORE;
- let includeMovies = true;
- let includeOVAs = true;
- let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL' = 'ORIGINAL';
- let includeOngoingMediaFromPreviousYears = false;
-
- $: {
- if (browser && mounted) {
- $page.url.searchParams.set('transparency', transparency.toString());
- $page.url.searchParams.set('lightTheme', lightTheme.toString());
- $page.url.searchParams.set('watermark', watermark.toString());
- $page.url.searchParams.set('includeMusic', includeMusic.toString());
- $page.url.searchParams.set('includeSpecials', includeSpecials.toString());
- $page.url.searchParams.set('includeRepeats', includeRepeats.toString());
- $page.url.searchParams.set('lightMode', lightMode.toString());
- $page.url.searchParams.set('highestRatedCount', highestRatedCount.toString());
- $page.url.searchParams.set('genreTagCount', genreTagCount.toString());
- $page.url.searchParams.set('disableActivityHistory', disableActivityHistory.toString());
- $page.url.searchParams.set(
- 'highestRatedMediaPercentage',
- highestRatedMediaPercentage.toString()
- );
- $page.url.searchParams.set(
- 'highestRatedGenreTagPercentage',
- highestRatedGenreTagPercentage.toString()
- );
- $page.url.searchParams.set('genreTagsSort', genreTagsSort.toString());
- $page.url.searchParams.set('mediaSort', mediaSort.toString());
- $page.url.searchParams.set('includeMovies', includeMovies.toString());
- $page.url.searchParams.set('includeOVAs', includeOVAs.toString());
-
- history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
- }
- }
- $: {
- includeMusic = includeMusic;
- includeSpecials = includeSpecials;
- includeRepeats = includeRepeats;
- disableActivityHistory = disableActivityHistory;
- highestRatedMediaPercentage = highestRatedMediaPercentage;
- highestRatedGenreTagPercentage = highestRatedGenreTagPercentage;
- topGenresTags = topGenresTags;
- genreTagsSort = genreTagsSort;
- mediaSort = mediaSort;
- includeMovies = includeMovies;
- includeOVAs = includeOVAs;
- selectedYear = selectedYear;
- includeOngoingMediaFromPreviousYears = includeOngoingMediaFromPreviousYears;
-
- update().then(updateWidth).catch(updateWidth);
- }
- $: {
- animeList = animeList;
- mangaList = mangaList;
- highestRatedCount = highestRatedCount;
-
- new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth);
- }
- $: {
- genreTagCount = genreTagCount;
-
- if (animeList && mangaList)
- topMedia = tops(
- [...(animeList || []), ...(mangaList || [])],
- genreTagCount,
- genreTagsSort,
- excludedKeywords
- );
-
- new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth);
- }
- $: {
- excludedKeywords = excludedKeywords;
-
- if (excludedKeywords.length > 0 && animeList !== undefined && mangaList !== undefined) {
- animeList = originalAnimeList;
- mangaList = originalMangaList;
- animeList = excludeKeywords(animeList as Media[]);
- mangaList = excludeKeywords(mangaList as Media[]);
- }
-
- updateWidth();
- }
- $: genreTagTitle = (() => {
- switch (genreTagsSort) {
- case SortOptions.SCORE:
- return 'Highest Rated';
- case SortOptions.MINUTES_WATCHED:
- return 'Most Watched';
- case SortOptions.COUNT:
- return 'Most Common';
- }
- })();
- $: animeMostTitle = (() => {
- switch (mediaSort) {
- case SortOptions.SCORE:
- return 'Highest Rated';
- case SortOptions.MINUTES_WATCHED:
- return 'Most Watched';
- case SortOptions.COUNT:
- return 'Most Common';
- }
- })();
- $: mangaMostTitle = (() => {
- switch (mediaSort) {
- case SortOptions.SCORE:
- return 'Highest Rated';
- case SortOptions.MINUTES_WATCHED:
- return 'Most Read';
- case SortOptions.COUNT:
- return 'Most Common';
- }
- })();
-
- const updateWidth = () => {
- if (!browser) return;
-
- const wrappedContainer = document.querySelector('#wrapped') as HTMLElement;
-
- if (!wrappedContainer) return;
-
- wrappedContainer.style.width = `1920px`;
-
- const reset = () => {
- let topWidths = 0;
- let middleWidths = 0;
- let bottomWidths = 0;
-
- wrappedContainer.querySelectorAll('.category').forEach((item) => {
- const category = item as HTMLElement;
- const style = window.getComputedStyle(category);
- const width =
- category.offsetWidth +
- parseFloat(style.marginLeft) +
- parseFloat(style.marginRight) +
- parseFloat(style.paddingLeft) +
- parseFloat(style.paddingRight) +
- parseFloat(style.borderLeftWidth) +
- parseFloat(style.borderRightWidth);
-
- if (category.classList.contains('top-category')) {
- topWidths += width;
- } else if (category.classList.contains('middle-category')) {
- middleWidths += width;
- } else if (category.classList.contains('bottom-category')) {
- bottomWidths += width;
- }
- });
-
- let requiredWidth = topWidths > middleWidths ? topWidths : middleWidths;
-
- if (!disableActivityHistory && bottomWidths > requiredWidth) requiredWidth = bottomWidths;
-
- requiredWidth += wrappedContainer.offsetWidth - wrappedContainer.clientWidth;
-
- wrappedContainer.style.width = `${requiredWidth}px`;
- width = requiredWidth;
- };
-
- reset();
- reset();
- };
-
- onMount(async () => {
- clearAllParameters([
- 'transparency',
- 'lightTheme',
- 'watermark',
- 'includeMusic',
- 'includeSpecials',
- 'includeRepeats',
- 'forceDark',
- 'highestRatedCount',
- 'genreTagCount',
- 'disableActivityHistory',
- 'highestRatedMediaPercentage',
- 'highestRatedGenreTagPercentage',
- 'genreTagsSort',
- 'mediaSort',
- 'includeMovies',
- 'includeOVAs'
- ]);
-
- if (browser) {
- transparency = $page.url.searchParams.get('transparency') === 'true';
- lightTheme = $page.url.searchParams.get('lightTheme') === 'true';
- watermark = $page.url.searchParams.get('watermark') === 'true';
- includeMusic = $page.url.searchParams.get('includeMusic') === 'true';
- includeSpecials = $page.url.searchParams.get('includeSpecials') === 'true';
- includeRepeats = $page.url.searchParams.get('includeRepeats') === 'true';
- lightMode = $page.url.searchParams.get('lightMode') === 'true';
- highestRatedCount = parseInt($page.url.searchParams.get('highestRatedCount') || '5', 10);
- genreTagCount = parseInt($page.url.searchParams.get('genreTagCount') || '5', 10);
- disableActivityHistory = $page.url.searchParams.get('disableActivityHistory') === 'true';
- highestRatedMediaPercentage =
- $page.url.searchParams.get('highestRatedMediaPercentage') === 'true';
- highestRatedGenreTagPercentage =
- $page.url.searchParams.get('highestRatedGenreTagPercentage') === 'true';
- // genreTagsSort = parseInt($page.url.searchParams.get('genreTagsSort') || '0', 10);
- // mediaSort = parseInt($page.url.searchParams.get('mediaSort') || '0', 10);
- includeMovies = $page.url.searchParams.get('includeMovies') === 'true';
- includeOVAs = $page.url.searchParams.get('includeOVAs') === 'true';
- }
-
- await update().then(() => (mounted = true));
- });
-
- const update = async () => {
- if ($userIdentity.id === -1) return;
-
- let rawAnimeList = await mediaListCollection(
- user,
- $userIdentity,
- Type.Anime,
- $anime,
- $lastPruneTimes.anime,
- {
- forcePrune: true,
- includeCompleted: true,
- all: true
- }
- );
- calculatedAnimeList = rawAnimeList
- .filter(
- (item, index, self) =>
- self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
- (includeMusic ? true : item.format !== 'MUSIC') &&
- (includeRepeats
- ? true
- : item.startDate.year === selectedYear || item.endDate.year === selectedYear
- ? true
- : item.mediaListEntry?.repeat === 0) &&
- (item.mediaListEntry?.startedAt.year === selectedYear ||
- item.mediaListEntry?.completedAt.year === selectedYear ||
- ((item.mediaListEntry?.createdAt
- ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.progress >= 1
- : false)) &&
- (includeMovies ? true : item.format !== 'MOVIE') &&
- (includeSpecials ? true : item.format !== 'SPECIAL') &&
- (includeOVAs ? true : item.format !== 'OVA')
- )
- .sort((a, b) => {
- switch (mediaSort) {
- case SortOptions.MINUTES_WATCHED:
- if (a.duration === undefined || a.mediaListEntry?.progress === undefined) return 1;
- else if (b.duration === undefined || b.mediaListEntry?.progress === undefined)
- return -1;
- else
- return (
- b.duration * b.mediaListEntry.progress - a.duration * a.mediaListEntry.progress
- );
- case SortOptions.SCORE:
- default:
- if (a.mediaListEntry?.score === undefined) return 1;
- else if (b.mediaListEntry?.score === undefined) return -1;
- else return b.mediaListEntry?.score - a.mediaListEntry?.score;
- }
- });
-
- animeList = rawAnimeList
- .filter(
- (item, index, self) =>
- self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
- (includeMusic ? true : item.format !== 'MUSIC') &&
- (includeRepeats
- ? true
- : item.startDate.year === selectedYear || item.endDate.year === selectedYear
- ? true
- : item.mediaListEntry?.repeat === 0) &&
- (item.mediaListEntry?.startedAt.year === selectedYear ||
- item.mediaListEntry?.completedAt.year === selectedYear ||
- ((item.mediaListEntry?.createdAt
- ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.progress >= 1
- : false) ||
- (includeOngoingMediaFromPreviousYears
- ? (item.mediaListEntry?.updatedAt
- ? new Date(item.mediaListEntry?.updatedAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.status === 'CURRENT'
- : false
- : false)) &&
- (includeMovies ? true : item.format !== 'MOVIE') &&
- (includeSpecials ? true : item.format !== 'SPECIAL') &&
- (includeOVAs ? true : item.format !== 'OVA')
- )
- .sort((a, b) => {
- switch (mediaSort) {
- case SortOptions.MINUTES_WATCHED:
- if (a.duration === undefined || a.mediaListEntry?.progress === undefined) return 1;
- else if (b.duration === undefined || b.mediaListEntry?.progress === undefined)
- return -1;
- else
- return (
- b.duration * b.mediaListEntry.progress - a.duration * a.mediaListEntry.progress
- );
- case SortOptions.SCORE:
- default:
- if (a.mediaListEntry?.score === undefined) return 1;
- else if (b.mediaListEntry?.score === undefined) return -1;
- else return b.mediaListEntry?.score - a.mediaListEntry?.score;
- }
- });
-
- let rawMangaList = await mediaListCollection(
- user,
- $userIdentity,
- Type.Manga,
- $manga,
- $lastPruneTimes.manga,
- {
- forcePrune: true,
- includeCompleted: true,
- all: true
- }
- );
- calculatedMangaList = rawMangaList
- .filter(
- (item, index, self) =>
- self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
- (includeRepeats ? true : item.mediaListEntry?.repeat === 0) &&
- (item.mediaListEntry?.startedAt.year === selectedYear ||
- item.mediaListEntry?.completedAt.year === selectedYear ||
- ((item.mediaListEntry?.createdAt
- ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.progress >= 1
- : false))
- )
- .sort((a, b) => {
- if (a.mediaListEntry?.score === undefined) return 1;
- else if (b.mediaListEntry?.score === undefined) return -1;
- else return b.mediaListEntry?.score - a.mediaListEntry?.score;
- });
-
- mangaList = rawMangaList
- .filter(
- (item, index, self) =>
- self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
- (includeRepeats ? true : item.mediaListEntry?.repeat === 0) &&
- (item.mediaListEntry?.startedAt.year === selectedYear ||
- item.mediaListEntry?.completedAt.year === selectedYear ||
- ((item.mediaListEntry?.createdAt
- ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.progress >= 1
- : false) ||
- (includeOngoingMediaFromPreviousYears
- ? (item.mediaListEntry?.updatedAt
- ? new Date(item.mediaListEntry?.updatedAt * 1000).getFullYear() === selectedYear
- : false) && item.mediaListEntry
- ? item.mediaListEntry?.status === 'CURRENT'
- : false
- : false))
- )
- .sort((a, b) => {
- if (a.mediaListEntry?.score === undefined) return 1;
- else if (b.mediaListEntry?.score === undefined) return -1;
- else return b.mediaListEntry?.score - a.mediaListEntry?.score;
- });
-
- episodes = 0;
- minutesWatched = 0;
- chapters = 0;
-
- for (const media of calculatedAnimeList) {
- episodes += media.mediaListEntry?.progress || 0;
- minutesWatched += (media.mediaListEntry?.progress || 0) * media.duration || 0;
- }
-
- for (const media of calculatedMangaList) chapters += media.mediaListEntry?.progress || 0;
- };
-
- /* eslint-disable @typescript-eslint/no-explicit-any */
- // const year = (statistic: { startYears: any }) =>
- // statistic.startYears.find((y: { startYear: number }) => y.startYear === 2023);
-
- const screenshot = async () => {
- let element = document.querySelector('#wrapped') as HTMLElement;
-
- if (element !== null) {
- domToBlob(element, {
- backgroundColor: transparency ? 'transparent' : lightTheme ? '#edf1f5' : '#0b1622',
- quality: 1,
- scale: 2,
- fetch: {
- requestInit: {
- mode: 'cors'
- },
- bypassingCache: true
- }
- }).then((blob) => {
- const downloadWrapper = document.createElement('a');
- // const wrappedImageButton = document.getElementById(
- // 'wrapped-image-download'
- // ) as HTMLAnchorElement;
- const image = document.createElement('img');
- const object = (window.URL || window.webkitURL || window || {}).createObjectURL(blob);
-
- // downloadWrapper.download = `due_dot_moe_wrapped_${dark ? 'dark' : 'light'}.png`;
- downloadWrapper.href = object;
- downloadWrapper.target = '_blank';
- image.src = object;
-
- downloadWrapper.appendChild(image);
-
- // if (wrappedImageButton !== null) {
- // wrappedImageButton.href = object;
- // }
-
- const wrappedFinal = document.getElementById('wrapped-final');
-
- if (wrappedFinal !== null) {
- wrappedFinal.innerHTML = '';
-
- wrappedFinal.appendChild(downloadWrapper);
-
- generated = true;
- }
-
- downloadWrapper.click();
- });
- }
- };
-
- // const abbreviate = (string: string, maxLength = 40, enabled = true) => {
- // if (!enabled) {
- // return string;
- // }
-
- // if (string.length <= maxLength) {
- // return string;
- // }
-
- // return string.slice(0, maxLength - 3) + ' …';
- // };
-
- const submitExcludedKeywords = () => {
- if (excludedKeywordsInput.length <= 0 && excludedKeywords.length > 0) {
- animeList = originalAnimeList;
- mangaList = originalMangaList;
- excludedKeywords = [];
- } else if (excludedKeywordsInput.length >= 0 && excludedKeywords.length <= 0) {
- originalAnimeList = animeList;
- originalMangaList = mangaList;
- }
-
- if (excludedKeywordsInput.length > 0)
- excludedKeywords = excludedKeywordsInput
- .split(',')
- .map((k) => k.trim())
- .filter((k) => k.length > 0);
- };
-
- const excludeKeywords = (media: Media[]) => {
- if (excludedKeywords.length <= 0) return media;
-
- return media.filter((m) => {
- for (const keyword of excludedKeywords) {
- if (m.title.english?.toLowerCase().includes(keyword.toLowerCase())) return false;
- if (m.title.romaji?.toLowerCase().includes(keyword.toLowerCase())) return false;
- if (m.title.native?.toLowerCase().includes(keyword.toLowerCase())) return false;
- }
-
- return true;
- });
- };
-
- const pruneFullYear = async () => {
- await database.activities.bulkDelete((await database.activities.toArray()).map((m) => m.page));
- };
-
- // const mergeArraySort = (a: any, b: any, mode: 'tags' | 'genres') => {
- // let merged = [...a, ...b].sort((a, b) => b.meanScore - a.meanScore);
-
- // merged = merged.filter(
- // (item, index, self) =>
- // self.findIndex((itemToCompare) =>
- // mode === 'genres'
- // ? itemToCompare.genre === item.genre
- // : itemToCompare.tag.name === item.tag.name
- // ) === index
- // );
-
- // return merged;
- // };
-
- // const randomCoverFromTop10 = (
- // statistics: { anime: any; manga: any },
- // mode: 'tags' | 'genres'
- // ) => {
- // const top = mergeArraySort(statistics.anime[mode], statistics.manga[mode], mode);
-
- // return mediaCover(top[Math.floor(Math.random() * top.length)].mediaIds[0]);
- // };
+ 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 {
+ fullActivityHistory,
+ activityHistory as getActivityHistory
+ } from '$lib/Data/AniList/activity';
+ import { Type, mediaListCollection, type Media } from '$lib/Data/AniList/media';
+ import anime from '$stores/anime';
+ import lastPruneTimes from '$stores/lastPruneTimes';
+ import manga from '$stores/manga';
+ import Error from '$lib/Error/RateLimited.svelte';
+ import { domToBlob } from 'modern-screenshot';
+ import { browser } from '$app/environment';
+ import { page } from '$app/stores';
+ import { clearAllParameters } from '../../Utility/parameters';
+ import SettingHint from '$lib/Settings/SettingHint.svelte';
+ import { database } from '$lib/Database/IDB/activities';
+ import Activity from './Top/Activity.svelte';
+ import Anime from './Top/Anime.svelte';
+ import Manga from './Top/Manga.svelte';
+ import ActivityHistory from './ActivityHistory.svelte';
+ import MediaExtras from './MediaExtras.svelte';
+ import MediaPanel from './Media.svelte';
+ import Watermark from './Watermark.svelte';
+ import Skeleton from '$lib/Loading/Skeleton.svelte';
+ import Message from '$lib/Loading/Message.svelte';
+ import tooltip from '$lib/Tooltip/tooltip';
+ import LogInRestricted from '$lib/Error/LogInRestricted.svelte';
+
+ export let user: AniListAuthorisation;
+
+ const currentYear = new Date(Date.now()).getFullYear();
+ let selectedYear = new Date(Date.now()).getFullYear();
+ let episodes = 0;
+ let chapters = 0;
+ let minutesWatched = 0;
+ let animeList: Media[] | undefined = undefined;
+ let mangaList: Media[] | undefined = undefined;
+ let calculatedAnimeList: Media[] | undefined = undefined;
+ let calculatedMangaList: Media[] | undefined = undefined;
+ let originalAnimeList: Media[] | undefined = undefined;
+ let originalMangaList: Media[] | undefined = undefined;
+ let transparency = false;
+ let lightTheme = true;
+ let watermark = false;
+ let includeMusic = false;
+ let includeSpecials = true;
+ let includeRepeats = false;
+ let width = 1920;
+ let lightMode = false;
+ let highestRatedCount = 5;
+ let genreTagCount = 5;
+ let mounted = false;
+ let generated = false;
+ let disableActivityHistory = true;
+ let excludedKeywordsInput = '';
+ let excludedKeywords: string[] = [];
+ let useFullActivityHistory = false;
+ let topGenresTags = true;
+ let topMedia: TopMedia;
+ let highestRatedMediaPercentage = true;
+ let highestRatedGenreTagPercentage = true;
+ let genreTagsSort = SortOptions.SCORE;
+ let mediaSort = SortOptions.SCORE;
+ let includeMovies = true;
+ let includeOVAs = true;
+ let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL' = 'ORIGINAL';
+ let includeOngoingMediaFromPreviousYears = false;
+
+ $: {
+ if (browser && mounted) {
+ $page.url.searchParams.set('transparency', transparency.toString());
+ $page.url.searchParams.set('lightTheme', lightTheme.toString());
+ $page.url.searchParams.set('watermark', watermark.toString());
+ $page.url.searchParams.set('includeMusic', includeMusic.toString());
+ $page.url.searchParams.set('includeSpecials', includeSpecials.toString());
+ $page.url.searchParams.set('includeRepeats', includeRepeats.toString());
+ $page.url.searchParams.set('lightMode', lightMode.toString());
+ $page.url.searchParams.set('highestRatedCount', highestRatedCount.toString());
+ $page.url.searchParams.set('genreTagCount', genreTagCount.toString());
+ $page.url.searchParams.set('disableActivityHistory', disableActivityHistory.toString());
+ $page.url.searchParams.set(
+ 'highestRatedMediaPercentage',
+ highestRatedMediaPercentage.toString()
+ );
+ $page.url.searchParams.set(
+ 'highestRatedGenreTagPercentage',
+ highestRatedGenreTagPercentage.toString()
+ );
+ $page.url.searchParams.set('genreTagsSort', genreTagsSort.toString());
+ $page.url.searchParams.set('mediaSort', mediaSort.toString());
+ $page.url.searchParams.set('includeMovies', includeMovies.toString());
+ $page.url.searchParams.set('includeOVAs', includeOVAs.toString());
+
+ history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
+ }
+ }
+ $: {
+ includeMusic = includeMusic;
+ includeSpecials = includeSpecials;
+ includeRepeats = includeRepeats;
+ disableActivityHistory = disableActivityHistory;
+ highestRatedMediaPercentage = highestRatedMediaPercentage;
+ highestRatedGenreTagPercentage = highestRatedGenreTagPercentage;
+ topGenresTags = topGenresTags;
+ genreTagsSort = genreTagsSort;
+ mediaSort = mediaSort;
+ includeMovies = includeMovies;
+ includeOVAs = includeOVAs;
+ selectedYear = selectedYear;
+ includeOngoingMediaFromPreviousYears = includeOngoingMediaFromPreviousYears;
+
+ update().then(updateWidth).catch(updateWidth);
+ }
+ $: {
+ animeList = animeList;
+ mangaList = mangaList;
+ highestRatedCount = highestRatedCount;
+
+ new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth);
+ }
+ $: {
+ genreTagCount = genreTagCount;
+
+ if (animeList && mangaList)
+ topMedia = tops(
+ [...(animeList || []), ...(mangaList || [])],
+ genreTagCount,
+ genreTagsSort,
+ excludedKeywords
+ );
+
+ new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth);
+ }
+ $: {
+ excludedKeywords = excludedKeywords;
+
+ if (excludedKeywords.length > 0 && animeList !== undefined && mangaList !== undefined) {
+ animeList = originalAnimeList;
+ mangaList = originalMangaList;
+ animeList = excludeKeywords(animeList as Media[]);
+ mangaList = excludeKeywords(mangaList as Media[]);
+ }
+
+ updateWidth();
+ }
+ $: genreTagTitle = (() => {
+ switch (genreTagsSort) {
+ case SortOptions.SCORE:
+ return 'Highest Rated';
+ case SortOptions.MINUTES_WATCHED:
+ return 'Most Watched';
+ case SortOptions.COUNT:
+ return 'Most Common';
+ }
+ })();
+ $: animeMostTitle = (() => {
+ switch (mediaSort) {
+ case SortOptions.SCORE:
+ return 'Highest Rated';
+ case SortOptions.MINUTES_WATCHED:
+ return 'Most Watched';
+ case SortOptions.COUNT:
+ return 'Most Common';
+ }
+ })();
+ $: mangaMostTitle = (() => {
+ switch (mediaSort) {
+ case SortOptions.SCORE:
+ return 'Highest Rated';
+ case SortOptions.MINUTES_WATCHED:
+ return 'Most Read';
+ case SortOptions.COUNT:
+ return 'Most Common';
+ }
+ })();
+
+ const updateWidth = () => {
+ if (!browser) return;
+
+ const wrappedContainer = document.querySelector('#wrapped') as HTMLElement;
+
+ if (!wrappedContainer) return;
+
+ wrappedContainer.style.width = `1920px`;
+
+ const reset = () => {
+ let topWidths = 0;
+ let middleWidths = 0;
+ let bottomWidths = 0;
+
+ wrappedContainer.querySelectorAll('.category').forEach((item) => {
+ const category = item as HTMLElement;
+ const style = window.getComputedStyle(category);
+ const width =
+ category.offsetWidth +
+ parseFloat(style.marginLeft) +
+ parseFloat(style.marginRight) +
+ parseFloat(style.paddingLeft) +
+ parseFloat(style.paddingRight) +
+ parseFloat(style.borderLeftWidth) +
+ parseFloat(style.borderRightWidth);
+
+ if (category.classList.contains('top-category')) {
+ topWidths += width;
+ } else if (category.classList.contains('middle-category')) {
+ middleWidths += width;
+ } else if (category.classList.contains('bottom-category')) {
+ bottomWidths += width;
+ }
+ });
+
+ let requiredWidth = topWidths > middleWidths ? topWidths : middleWidths;
+
+ if (!disableActivityHistory && bottomWidths > requiredWidth) requiredWidth = bottomWidths;
+
+ requiredWidth += wrappedContainer.offsetWidth - wrappedContainer.clientWidth;
+
+ wrappedContainer.style.width = `${requiredWidth}px`;
+ width = requiredWidth;
+ };
+
+ reset();
+ reset();
+ };
+
+ onMount(async () => {
+ clearAllParameters([
+ 'transparency',
+ 'lightTheme',
+ 'watermark',
+ 'includeMusic',
+ 'includeSpecials',
+ 'includeRepeats',
+ 'forceDark',
+ 'highestRatedCount',
+ 'genreTagCount',
+ 'disableActivityHistory',
+ 'highestRatedMediaPercentage',
+ 'highestRatedGenreTagPercentage',
+ 'genreTagsSort',
+ 'mediaSort',
+ 'includeMovies',
+ 'includeOVAs'
+ ]);
+
+ if (browser) {
+ transparency = $page.url.searchParams.get('transparency') === 'true';
+ lightTheme = $page.url.searchParams.get('lightTheme') === 'true';
+ watermark = $page.url.searchParams.get('watermark') === 'true';
+ includeMusic = $page.url.searchParams.get('includeMusic') === 'true';
+ includeSpecials = $page.url.searchParams.get('includeSpecials') === 'true';
+ includeRepeats = $page.url.searchParams.get('includeRepeats') === 'true';
+ lightMode = $page.url.searchParams.get('lightMode') === 'true';
+ highestRatedCount = parseInt($page.url.searchParams.get('highestRatedCount') || '5', 10);
+ genreTagCount = parseInt($page.url.searchParams.get('genreTagCount') || '5', 10);
+ disableActivityHistory = $page.url.searchParams.get('disableActivityHistory') === 'true';
+ highestRatedMediaPercentage =
+ $page.url.searchParams.get('highestRatedMediaPercentage') === 'true';
+ highestRatedGenreTagPercentage =
+ $page.url.searchParams.get('highestRatedGenreTagPercentage') === 'true';
+ // genreTagsSort = parseInt($page.url.searchParams.get('genreTagsSort') || '0', 10);
+ // mediaSort = parseInt($page.url.searchParams.get('mediaSort') || '0', 10);
+ includeMovies = $page.url.searchParams.get('includeMovies') === 'true';
+ includeOVAs = $page.url.searchParams.get('includeOVAs') === 'true';
+ }
+
+ await update().then(() => (mounted = true));
+ });
+
+ const update = async () => {
+ if ($userIdentity.id === -1) return;
+
+ let rawAnimeList = await mediaListCollection(
+ user,
+ $userIdentity,
+ Type.Anime,
+ $anime,
+ $lastPruneTimes.anime,
+ {
+ forcePrune: true,
+ includeCompleted: true,
+ all: true
+ }
+ );
+ calculatedAnimeList = rawAnimeList
+ .filter(
+ (item, index, self) =>
+ self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
+ (includeMusic ? true : item.format !== 'MUSIC') &&
+ (includeRepeats
+ ? true
+ : item.startDate.year === selectedYear || item.endDate.year === selectedYear
+ ? true
+ : item.mediaListEntry?.repeat === 0) &&
+ (item.mediaListEntry?.startedAt.year === selectedYear ||
+ item.mediaListEntry?.completedAt.year === selectedYear ||
+ ((item.mediaListEntry?.createdAt
+ ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.progress >= 1
+ : false)) &&
+ (includeMovies ? true : item.format !== 'MOVIE') &&
+ (includeSpecials ? true : item.format !== 'SPECIAL') &&
+ (includeOVAs ? true : item.format !== 'OVA')
+ )
+ .sort((a, b) => {
+ switch (mediaSort) {
+ case SortOptions.MINUTES_WATCHED:
+ if (a.duration === undefined || a.mediaListEntry?.progress === undefined) return 1;
+ else if (b.duration === undefined || b.mediaListEntry?.progress === undefined)
+ return -1;
+ else
+ return (
+ b.duration * b.mediaListEntry.progress - a.duration * a.mediaListEntry.progress
+ );
+ case SortOptions.SCORE:
+ default:
+ if (a.mediaListEntry?.score === undefined) return 1;
+ else if (b.mediaListEntry?.score === undefined) return -1;
+ else return b.mediaListEntry?.score - a.mediaListEntry?.score;
+ }
+ });
+
+ animeList = rawAnimeList
+ .filter(
+ (item, index, self) =>
+ self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
+ (includeMusic ? true : item.format !== 'MUSIC') &&
+ (includeRepeats
+ ? true
+ : item.startDate.year === selectedYear || item.endDate.year === selectedYear
+ ? true
+ : item.mediaListEntry?.repeat === 0) &&
+ (item.mediaListEntry?.startedAt.year === selectedYear ||
+ item.mediaListEntry?.completedAt.year === selectedYear ||
+ ((item.mediaListEntry?.createdAt
+ ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.progress >= 1
+ : false) ||
+ (includeOngoingMediaFromPreviousYears
+ ? (item.mediaListEntry?.updatedAt
+ ? new Date(item.mediaListEntry?.updatedAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.status === 'CURRENT'
+ : false
+ : false)) &&
+ (includeMovies ? true : item.format !== 'MOVIE') &&
+ (includeSpecials ? true : item.format !== 'SPECIAL') &&
+ (includeOVAs ? true : item.format !== 'OVA')
+ )
+ .sort((a, b) => {
+ switch (mediaSort) {
+ case SortOptions.MINUTES_WATCHED:
+ if (a.duration === undefined || a.mediaListEntry?.progress === undefined) return 1;
+ else if (b.duration === undefined || b.mediaListEntry?.progress === undefined)
+ return -1;
+ else
+ return (
+ b.duration * b.mediaListEntry.progress - a.duration * a.mediaListEntry.progress
+ );
+ case SortOptions.SCORE:
+ default:
+ if (a.mediaListEntry?.score === undefined) return 1;
+ else if (b.mediaListEntry?.score === undefined) return -1;
+ else return b.mediaListEntry?.score - a.mediaListEntry?.score;
+ }
+ });
+
+ let rawMangaList = await mediaListCollection(
+ user,
+ $userIdentity,
+ Type.Manga,
+ $manga,
+ $lastPruneTimes.manga,
+ {
+ forcePrune: true,
+ includeCompleted: true,
+ all: true
+ }
+ );
+ calculatedMangaList = rawMangaList
+ .filter(
+ (item, index, self) =>
+ self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
+ (includeRepeats ? true : item.mediaListEntry?.repeat === 0) &&
+ (item.mediaListEntry?.startedAt.year === selectedYear ||
+ item.mediaListEntry?.completedAt.year === selectedYear ||
+ ((item.mediaListEntry?.createdAt
+ ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.progress >= 1
+ : false))
+ )
+ .sort((a, b) => {
+ if (a.mediaListEntry?.score === undefined) return 1;
+ else if (b.mediaListEntry?.score === undefined) return -1;
+ else return b.mediaListEntry?.score - a.mediaListEntry?.score;
+ });
+
+ mangaList = rawMangaList
+ .filter(
+ (item, index, self) =>
+ self.findIndex((itemToCompare) => itemToCompare.id === item.id) === index &&
+ (includeRepeats ? true : item.mediaListEntry?.repeat === 0) &&
+ (item.mediaListEntry?.startedAt.year === selectedYear ||
+ item.mediaListEntry?.completedAt.year === selectedYear ||
+ ((item.mediaListEntry?.createdAt
+ ? new Date(item.mediaListEntry?.createdAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.progress >= 1
+ : false) ||
+ (includeOngoingMediaFromPreviousYears
+ ? (item.mediaListEntry?.updatedAt
+ ? new Date(item.mediaListEntry?.updatedAt * 1000).getFullYear() === selectedYear
+ : false) && item.mediaListEntry
+ ? item.mediaListEntry?.status === 'CURRENT'
+ : false
+ : false))
+ )
+ .sort((a, b) => {
+ if (a.mediaListEntry?.score === undefined) return 1;
+ else if (b.mediaListEntry?.score === undefined) return -1;
+ else return b.mediaListEntry?.score - a.mediaListEntry?.score;
+ });
+
+ episodes = 0;
+ minutesWatched = 0;
+ chapters = 0;
+
+ for (const media of calculatedAnimeList) {
+ episodes += media.mediaListEntry?.progress || 0;
+ minutesWatched += (media.mediaListEntry?.progress || 0) * media.duration || 0;
+ }
+
+ for (const media of calculatedMangaList) chapters += media.mediaListEntry?.progress || 0;
+ };
+
+ /* eslint-disable @typescript-eslint/no-explicit-any */
+ // const year = (statistic: { startYears: any }) =>
+ // statistic.startYears.find((y: { startYear: number }) => y.startYear === 2023);
+
+ const screenshot = async () => {
+ let element = document.querySelector('#wrapped') as HTMLElement;
+
+ if (element !== null) {
+ domToBlob(element, {
+ backgroundColor: transparency ? 'transparent' : lightTheme ? '#edf1f5' : '#0b1622',
+ quality: 1,
+ scale: 2,
+ fetch: {
+ requestInit: {
+ mode: 'cors'
+ },
+ bypassingCache: true
+ }
+ }).then((blob) => {
+ const downloadWrapper = document.createElement('a');
+ // const wrappedImageButton = document.getElementById(
+ // 'wrapped-image-download'
+ // ) as HTMLAnchorElement;
+ const image = document.createElement('img');
+ const object = (window.URL || window.webkitURL || window || {}).createObjectURL(blob);
+
+ // downloadWrapper.download = `due_dot_moe_wrapped_${dark ? 'dark' : 'light'}.png`;
+ downloadWrapper.href = object;
+ downloadWrapper.target = '_blank';
+ image.src = object;
+
+ downloadWrapper.appendChild(image);
+
+ // if (wrappedImageButton !== null) {
+ // wrappedImageButton.href = object;
+ // }
+
+ const wrappedFinal = document.getElementById('wrapped-final');
+
+ if (wrappedFinal !== null) {
+ wrappedFinal.innerHTML = '';
+
+ wrappedFinal.appendChild(downloadWrapper);
+
+ generated = true;
+ }
+
+ downloadWrapper.click();
+ });
+ }
+ };
+
+ // const abbreviate = (string: string, maxLength = 40, enabled = true) => {
+ // if (!enabled) {
+ // return string;
+ // }
+
+ // if (string.length <= maxLength) {
+ // return string;
+ // }
+
+ // return string.slice(0, maxLength - 3) + ' …';
+ // };
+
+ const submitExcludedKeywords = () => {
+ if (excludedKeywordsInput.length <= 0 && excludedKeywords.length > 0) {
+ animeList = originalAnimeList;
+ mangaList = originalMangaList;
+ excludedKeywords = [];
+ } else if (excludedKeywordsInput.length >= 0 && excludedKeywords.length <= 0) {
+ originalAnimeList = animeList;
+ originalMangaList = mangaList;
+ }
+
+ if (excludedKeywordsInput.length > 0)
+ excludedKeywords = excludedKeywordsInput
+ .split(',')
+ .map((k) => k.trim())
+ .filter((k) => k.length > 0);
+ };
+
+ const excludeKeywords = (media: Media[]) => {
+ if (excludedKeywords.length <= 0) return media;
+
+ return media.filter((m) => {
+ for (const keyword of excludedKeywords) {
+ if (m.title.english?.toLowerCase().includes(keyword.toLowerCase())) return false;
+ if (m.title.romaji?.toLowerCase().includes(keyword.toLowerCase())) return false;
+ if (m.title.native?.toLowerCase().includes(keyword.toLowerCase())) return false;
+ }
+
+ return true;
+ });
+ };
+
+ const pruneFullYear = async () => {
+ await database.activities.bulkDelete((await database.activities.toArray()).map((m) => m.page));
+ };
+
+ // const mergeArraySort = (a: any, b: any, mode: 'tags' | 'genres') => {
+ // let merged = [...a, ...b].sort((a, b) => b.meanScore - a.meanScore);
+
+ // merged = merged.filter(
+ // (item, index, self) =>
+ // self.findIndex((itemToCompare) =>
+ // mode === 'genres'
+ // ? itemToCompare.genre === item.genre
+ // : itemToCompare.tag.name === item.tag.name
+ // ) === index
+ // );
+
+ // return merged;
+ // };
+
+ // const randomCoverFromTop10 = (
+ // statistics: { anime: any; manga: any },
+ // mode: 'tags' | 'genres'
+ // ) => {
+ // const top = mergeArraySort(statistics.anime[mode], statistics.manga[mode], mode);
+
+ // return mediaCover(top[Math.floor(Math.random() * top.length)].mediaIds[0]);
+ // };
</script>
{#if $userIdentity.id === -2 || user === undefined}
- <LogInRestricted />
+ <LogInRestricted />
{:else if $userIdentity.id !== -1}
- {#await selectedYear !== currentYear || useFullActivityHistory || new Date().getMonth() <= 6 ? fullActivityHistory(user, $userIdentity, selectedYear) : getActivityHistory($userIdentity)}
- <Message message="Loading activity history ..." />
-
- <Skeleton count={2} />
- {:then activities}
- {#await wrapped(user, $userIdentity, selectedYear)}
- <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>
- {#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>
- <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}
-
- <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 />
- <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="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>
- </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}
- </Error>
- {/await}
+ {#await selectedYear !== currentYear || useFullActivityHistory || new Date().getMonth() <= 6 ? fullActivityHistory(user, $userIdentity, selectedYear) : getActivityHistory($userIdentity)}
+ <Message message="Loading activity history ..." />
+
+ <Skeleton count={2} />
+ {:then activities}
+ {#await wrapped(user, $userIdentity, selectedYear)}
+ <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>
+ {#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>
+ <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}
+
+ <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 />
+ <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="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>
+ </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}
+ </Error>
+ {/await}
{:else}
- <Message message="Loading user ..." />
+ <Message message="Loading user ..." />
- <Skeleton count={2} />
+ <Skeleton count={2} />
{/if}
<style>
- @import './wrapped.css';
+ @import './wrapped.css';
</style>
diff --git a/src/lib/Tools/Wrapped/Top/Activity.svelte b/src/lib/Tools/Wrapped/Top/Activity.svelte
index 03f15b5d..a91bedfb 100644
--- a/src/lib/Tools/Wrapped/Top/Activity.svelte
+++ b/src/lib/Tools/Wrapped/Top/Activity.svelte
@@ -1,42 +1,42 @@
<script lang="ts">
- import type { ActivityHistoryEntry } from '$lib/Data/AniList/activity';
- import identity from '$stores/identity';
- import type { Wrapped } from '$lib/Data/AniList/wrapped';
- import proxy from '$lib/Utility/proxy';
+ import type { ActivityHistoryEntry } from '$lib/Data/AniList/activity';
+ import identity from '$stores/identity';
+ import type { Wrapped } from '$lib/Data/AniList/wrapped';
+ import proxy from '$lib/Utility/proxy';
- export let wrapped: Wrapped;
- export let year: number;
- export let activities: ActivityHistoryEntry[];
- export let useFullActivityHistory: boolean;
- export let updateWidth: () => void;
+ export let wrapped: Wrapped;
+ export let year: number;
+ export let activities: ActivityHistoryEntry[];
+ export let useFullActivityHistory: boolean;
+ export let updateWidth: () => void;
- const currentYear = new Date(Date.now()).getFullYear();
+ const currentYear = new Date(Date.now()).getFullYear();
</script>
<div class="grid-item image-grid avatar-grid category top-category">
- <a href={`https://anilist.co/user/${$identity.name}`} target="_blank">
- <img src={proxy(wrapped.avatar.large)} alt="User Avatar" on:load={updateWidth} />
- </a>
- <div>
- <div>
- <a href={`https://anilist.co/user/${$identity.name}`} target="_blank">
- <b>
- {$identity.name}
- </b>
- </a>
- </div>
- <div>
- Status Posts: {wrapped.activities.statusCount}
- </div>
- <div>
- Messages: {wrapped.activities.messageCount}
- </div>
- <div>
- Days Active: {#if year !== currentYear}
- ?/365
- {:else}
- {activities.length}/{useFullActivityHistory ? 365 : 189}
- {/if}
- </div>
- </div>
+ <a href={`https://anilist.co/user/${$identity.name}`} target="_blank">
+ <img src={proxy(wrapped.avatar.large)} alt="User Avatar" on:load={updateWidth} />
+ </a>
+ <div>
+ <div>
+ <a href={`https://anilist.co/user/${$identity.name}`} target="_blank">
+ <b>
+ {$identity.name}
+ </b>
+ </a>
+ </div>
+ <div>
+ Status Posts: {wrapped.activities.statusCount}
+ </div>
+ <div>
+ Messages: {wrapped.activities.messageCount}
+ </div>
+ <div>
+ Days Active: {#if year !== currentYear}
+ ?/365
+ {:else}
+ {activities.length}/{useFullActivityHistory ? 365 : 189}
+ {/if}
+ </div>
+ </div>
</div>
diff --git a/src/lib/Tools/Wrapped/Top/Anime.svelte b/src/lib/Tools/Wrapped/Top/Anime.svelte
index b33a8c08..08df7fd3 100644
--- a/src/lib/Tools/Wrapped/Top/Anime.svelte
+++ b/src/lib/Tools/Wrapped/Top/Anime.svelte
@@ -1,20 +1,20 @@
<script lang="ts">
- import type { Media } from '$lib/Data/AniList/media';
+ import type { Media } from '$lib/Data/AniList/media';
- export let minutesWatched: number;
- export let animeList: Media[] | undefined;
- export let episodes: number;
+ export let minutesWatched: number;
+ export let animeList: Media[] | undefined;
+ export let episodes: number;
</script>
<div class="category-grid pure-category category top-category">
- <div class="grid-item">
- <b>Anime</b>
- </div>
- <div class="grid-item">
- Time Watched: {((minutesWatched || 0) / 60 / 24).toFixed(2)} days
- </div>
- <div class="grid-item">
- Completed: {animeList?.length || 0}
- </div>
- <div class="grid-item">Episodes: {episodes}</div>
+ <div class="grid-item">
+ <b>Anime</b>
+ </div>
+ <div class="grid-item">
+ Time Watched: {((minutesWatched || 0) / 60 / 24).toFixed(2)} days
+ </div>
+ <div class="grid-item">
+ Completed: {animeList?.length || 0}
+ </div>
+ <div class="grid-item">Episodes: {episodes}</div>
</div>
diff --git a/src/lib/Tools/Wrapped/Top/Manga.svelte b/src/lib/Tools/Wrapped/Top/Manga.svelte
index 6c505df2..a36f7724 100644
--- a/src/lib/Tools/Wrapped/Top/Manga.svelte
+++ b/src/lib/Tools/Wrapped/Top/Manga.svelte
@@ -1,22 +1,22 @@
<script lang="ts">
- import type { Media } from '$lib/Data/AniList/media';
- import { estimatedDayReading } from '$lib/Media/Manga/time';
+ import type { Media } from '$lib/Data/AniList/media';
+ import { estimatedDayReading } from '$lib/Media/Manga/time';
- export let mangaList: Media[] | undefined;
- export let chapters: number;
+ export let mangaList: Media[] | undefined;
+ export let chapters: number;
</script>
<div class="category-grid pure-category category top-category">
- <div class="grid-item">
- <b>Manga</b>
- </div>
- <div class="grid-item">
- Time Read: {estimatedDayReading(chapters).toFixed(2)} days
- </div>
- <div class="grid-item">
- Completed: {mangaList?.length || 0}
- </div>
- <div class="grid-item">
- Chapters: {chapters}
- </div>
+ <div class="grid-item">
+ <b>Manga</b>
+ </div>
+ <div class="grid-item">
+ Time Read: {estimatedDayReading(chapters).toFixed(2)} days
+ </div>
+ <div class="grid-item">
+ Completed: {mangaList?.length || 0}
+ </div>
+ <div class="grid-item">
+ Chapters: {chapters}
+ </div>
</div>
diff --git a/src/lib/Tools/Wrapped/Watermark.svelte b/src/lib/Tools/Wrapped/Watermark.svelte
index 2e8dd838..f166d554 100644
--- a/src/lib/Tools/Wrapped/Watermark.svelte
+++ b/src/lib/Tools/Wrapped/Watermark.svelte
@@ -1,5 +1,5 @@
<div class="categories-grid" style="padding-top: 0;">
- <div class="category-grid pure-category" id="watermark">
- <a href="https://due.moe/wrapped" target="_blank">due.moe/wrapped</a>
- </div>
+ <div class="category-grid pure-category" id="watermark">
+ <a href="https://due.moe/wrapped" target="_blank">due.moe/wrapped</a>
+ </div>
</div>
diff --git a/src/lib/Tools/Wrapped/wrapped.css b/src/lib/Tools/Wrapped/wrapped.css
index be42cd0a..c144a6f0 100644
--- a/src/lib/Tools/Wrapped/wrapped.css
+++ b/src/lib/Tools/Wrapped/wrapped.css
@@ -2,99 +2,99 @@
@import url('https://proxy.due.moe/?q=https://fonts.googleapis.com/css?family=Overpass:400,600,700,800');
.categories-grid {
- display: flex;
- flex-wrap: wrap;
- row-gap: 1.5em;
- column-gap: 1.5em;
- padding: 2%;
- justify-content: center;
- font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell,
- Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
- background-color: #0b1622;
+ display: flex;
+ flex-wrap: wrap;
+ row-gap: 1.5em;
+ column-gap: 1.5em;
+ padding: 2%;
+ justify-content: center;
+ font-family: Roboto, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell,
+ Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+ background-color: #0b1622;
}
.categories-grid b {
- font-family: Overpass, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell,
- Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
- font-weight: 600;
+ font-family: Overpass, -apple-system, BlinkMacSystemFont, Segoe UI, Oxygen, Ubuntu, Cantarell,
+ Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
+ font-weight: 600;
}
.category-grid,
.image-grid {
- background-color: #151f2e;
- border-radius: 4px;
- color: rgb(159, 173, 189);
+ background-color: #151f2e;
+ border-radius: 4px;
+ color: rgb(159, 173, 189);
}
.pure-category,
.avatar-grid {
- padding: 1.5%;
+ padding: 1.5%;
}
.category-grid {
- display: grid;
+ display: grid;
}
.image-grid {
- display: flex;
- column-gap: 1em;
- flex-wrap: wrap;
+ display: flex;
+ column-gap: 1em;
+ flex-wrap: wrap;
}
.image-grid img {
- width: 6em;
- height: auto;
- border-radius: 3px;
+ width: 6em;
+ height: auto;
+ border-radius: 3px;
}
.categories-grid a {
- text-decoration: none;
- color: unset;
+ text-decoration: none;
+ color: unset;
}
.transparent .categories-grid {
- background-color: transparent !important;
+ background-color: transparent !important;
}
.light-theme .categories-grid {
- background-color: #edf1f5;
+ background-color: #edf1f5;
}
.light-theme .category-grid {
- background-color: #fafafa;
- color: rgb(92, 114, 138);
+ background-color: #fafafa;
+ color: rgb(92, 114, 138);
}
.light-theme .image-grid {
- background-color: #fafafa;
- color: rgb(92, 114, 138);
+ background-color: #fafafa;
+ color: rgb(92, 114, 138);
}
ol {
- margin: 0 !important;
+ margin: 0 !important;
}
#watermark {
- color: rgb(61, 180, 242);
+ color: rgb(61, 180, 242);
}
#wrapped-final {
- height: auto;
- width: 50%;
+ height: auto;
+ width: 50%;
}
#list-container {
- display: flex;
- gap: 1rem;
- flex-wrap: wrap;
- align-items: start;
+ display: flex;
+ gap: 1rem;
+ flex-wrap: wrap;
+ align-items: start;
}
.list {
- flex-grow: 1;
- flex-basis: 1%;
+ flex-grow: 1;
+ flex-basis: 1%;
}
#wrapped {
- overflow-y: scroll;
+ overflow-y: scroll;
}