aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Tools/Wrapped.svelte
diff options
context:
space:
mode:
authorFuwn <[email protected]>2023-12-22 03:07:04 -0800
committerFuwn <[email protected]>2023-12-22 03:11:24 -0800
commita7255393ac86b091772189469fc1806ded1595d1 (patch)
treec238fcd2d5fa3302f195f9ee76d0d2dbbe1da43f /src/lib/Tools/Wrapped.svelte
parentfix(wrapped): absolute best updateWidth() (diff)
downloaddue.moe-a7255393ac86b091772189469fc1806ded1595d1.tar.xz
due.moe-a7255393ac86b091772189469fc1806ded1595d1.zip
feat(wrapped): full-year activity history
Diffstat (limited to 'src/lib/Tools/Wrapped.svelte')
-rw-r--r--src/lib/Tools/Wrapped.svelte378
1 files changed, 199 insertions, 179 deletions
diff --git a/src/lib/Tools/Wrapped.svelte b/src/lib/Tools/Wrapped.svelte
index dbd7cc1e..a3db5a06 100644
--- a/src/lib/Tools/Wrapped.svelte
+++ b/src/lib/Tools/Wrapped.svelte
@@ -6,7 +6,11 @@
} from '$lib/AniList/identity';
import { onMount } from 'svelte';
import { wrapped } from '$lib/AniList/wrapped.js';
- import { activityHistory as getActivityHistory, fillMissingDays } from '$lib/AniList/activity.js';
+ import {
+ activityHistory,
+ fullActivityHistory,
+ activityHistory as getActivityHistory
+ } from '$lib/AniList/activity.js';
import { Type, mediaListCollection, type Media } from '$lib/AniList/media.js';
import anime from '../../stores/anime.js';
import lastPruneTimes from '../../stores/lastPruneTimes.js';
@@ -45,6 +49,7 @@
let disableActivityHistory = true;
let excludedKeywordsInput = '';
let excludedKeywords: string[] = [];
+ let useFullActivityHistory = false;
$: {
if (browser && mounted) {
@@ -99,6 +104,7 @@
const reset = () => {
let topWidths = 0;
let middleWidths = 0;
+ let bottomWidths = 0;
wrappedContainer.querySelectorAll('.category').forEach((item) => {
const category = item as HTMLElement;
@@ -116,10 +122,14 @@
topWidths += width;
} else if (category.classList.contains('middle-category')) {
middleWidths += width;
+ } else if (category.classList.contains('bottom-category')) {
+ bottomWidths += width;
}
});
- const requiredWidth = topWidths > middleWidths ? topWidths : middleWidths;
+ let requiredWidth = topWidths > middleWidths ? topWidths : middleWidths;
+
+ if (bottomWidths > requiredWidth) requiredWidth = bottomWidths;
wrappedContainer.style.width = `${requiredWidth}px`;
width = requiredWidth;
@@ -330,210 +340,220 @@
return true;
});
};
+
+ const nbsp = (str: string) => str.replace(/ /g, '&nbsp;');
</script>
{#if currentUserIdentity.id === -2}
Please log in to view this page.
{:else if currentUserIdentity.id !== -1}
- {#await wrapped(user, currentUserIdentity)}
- Loading&nbsp;...
- {:then wrapped}
- <div
- id="wrapped"
- class:light-theme={lightMode}
- style={`width: ${width}px;`}
- class:transparent={transparency}
- >
- <div class="categories-grid" style="padding-bottom: 0;">
- <div class="grid-item image-grid avatar-grid category top-category">
- <a href={`https://anilist.co/user/${currentUserIdentity.name}`} target="_blank">
- <img src={proxy(wrapped.avatar.large)} alt="User Avatar" on:load={updateWidth} />
- </a>
- <div>
+ {#await useFullActivityHistory ? fullActivityHistory(user, currentUserIdentity) : getActivityHistory(currentUserIdentity)}
+ {@html nbsp(`Loading${useFullActivityHistory ? ' full-year' : ''} activity history ...`)}
+ {:then activities}
+ {#await wrapped(user, currentUserIdentity)}
+ Loading&nbsp;...
+ {:then wrapped}
+ <div
+ id="wrapped"
+ class:light-theme={lightMode}
+ style={`width: ${width}px;`}
+ class:transparent={transparency}
+ >
+ <div class="categories-grid" style="padding-bottom: 0;">
+ <div class="grid-item image-grid avatar-grid category top-category">
+ <a href={`https://anilist.co/user/${currentUserIdentity.name}`} target="_blank">
+ <img src={proxy(wrapped.avatar.large)} alt="User Avatar" on:load={updateWidth} />
+ </a>
<div>
- <a href={`https://anilist.co/user/${currentUserIdentity.name}`} target="_blank">
- <b>
- {currentUserIdentity.name}
- </b>
- </a>
+ <div>
+ <a href={`https://anilist.co/user/${currentUserIdentity.name}`} target="_blank">
+ <b>
+ {currentUserIdentity.name}
+ </b>
+ </a>
+ </div>
+ <div>
+ Status Posts: {wrapped.activities.statusCount}
+ </div>
+ <div>
+ Messages: {wrapped.activities.messageCount}
+ </div>
+ <div>
+ Days Active: {activities.length}/{useFullActivityHistory ? 365 : 189}
+ </div>
</div>
- <div>
- Status Posts: {wrapped.activities.statusCount}
+ </div>
+ <div class="category-grid pure-category category top-category">
+ <div class="grid-item">
+ <b>Anime</b>
</div>
- <div>
- Messages: {wrapped.activities.messageCount}
+ <div class="grid-item">
+ Time Watched: {((minutesWatched || 0) / 60 / 24).toFixed(2)} days
</div>
- <div>
- Days Active: {#await getActivityHistory(currentUserIdentity)}
- Loading&nbsp;...
- {:then activities}
- {#if activities === undefined}
- Loading&nbsp;...
- {:else}
- {fillMissingDays(activities, true).filter((a) => a.amount !== 0).length}/365
- {/if}
- {/await}
+ <div class="grid-item">
+ Completed: {animeList?.length}
</div>
+ <div class="grid-item">Episodes: {episodes}</div>
</div>
- </div>
- <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}
+ <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}
+ </div>
+ <div class="grid-item">
+ Chapters: {chapters}
+ </div>
</div>
- <div class="grid-item">Episodes: {episodes}</div>
</div>
- <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}
- </div>
- <div class="grid-item">
- Chapters: {chapters}
+ <div class="categories-grid">
+ <div class="category-grid pure-category category middle-category">
+ <div class="grid-item image-grid">
+ {#if animeList !== undefined}
+ <a href={`https://anilist.co/anime/${animeList[0].id}`} target="_blank">
+ <img
+ src={proxy(animeList[0].coverImage.extraLarge)}
+ alt="Highest Rated Anime Cover"
+ class="cover-image"
+ on:load={updateWidth}
+ />
+ </a>
+ <div>
+ <b>Highest Rated Anime</b>
+ <ol>
+ {#each animeList?.slice(0, highestRatedCount) as anime}
+ <li>
+ <a href={`https://anilist.co/anime/${anime.id}`} target="_blank">
+ {anime.title.english || anime.title.romaji || anime.title.native}
+ </a>
+ </li>
+ {/each}
+ </ol>
+ </div>
+ {:else}
+ Loading&nbsp;...
+ {/if}
+ </div>
</div>
- </div>
- </div>
- <div class="categories-grid">
- <div class="category-grid pure-category category middle-category">
- <div class="grid-item image-grid">
- {#if animeList !== undefined}
- <a href={`https://anilist.co/anime/${animeList[0].id}`} target="_blank">
- <img
- src={proxy(animeList[0].coverImage.extraLarge)}
- alt="Highest Rated Anime Cover"
- class="cover-image"
- on:load={updateWidth}
- />
- </a>
- <div>
- <b>Highest Rated Anime</b>
- <ol>
- {#each animeList?.slice(0, highestRatedCount) as anime}
- <li>
- <a href={`https://anilist.co/anime/${anime.id}`} target="_blank">
- {anime.title.english || anime.title.romaji || anime.title.native}
- </a>
- </li>
- {/each}
- </ol>
- </div>
- {:else}
- Loading&nbsp;...
- {/if}
+ <div class="category-grid pure-category category middle-category">
+ <div class="grid-item image-grid">
+ {#if mangaList !== undefined}
+ <a href={`https://anilist.co/manga/${mangaList[0].id}`} target="_blank">
+ <img
+ src={proxy(mangaList[0].coverImage.extraLarge)}
+ alt="Highest Rated Manga Cover"
+ class="cover-image"
+ on:load={updateWidth}
+ />
+ </a>
+ <div>
+ <b>Highest Rated Manga</b>
+ <ol>
+ {#each mangaList?.slice(0, highestRatedCount) as manga}
+ <li>
+ <a href={`https://anilist.co/manga/${manga.id}`} target="_blank">
+ {manga.title.english || manga.title.romaji || manga.title.native}
+ </a>
+ </li>
+ {/each}
+ </ol>
+ </div>
+ {:else}
+ Loading&nbsp;...
+ {/if}
+ </div>
</div>
</div>
- <div class="category-grid pure-category category middle-category">
- <div class="grid-item image-grid">
- {#if mangaList !== undefined}
- <a href={`https://anilist.co/manga/${mangaList[0].id}`} target="_blank">
- <img
- src={proxy(mangaList[0].coverImage.extraLarge)}
- alt="Highest Rated Manga Cover"
- class="cover-image"
- on:load={updateWidth}
- />
- </a>
- <div>
- <b>Highest Rated Manga</b>
- <ol>
- {#each mangaList?.slice(0, highestRatedCount) as manga}
- <li>
- <a href={`https://anilist.co/manga/${manga.id}`} target="_blank">
- {manga.title.english || manga.title.romaji || manga.title.native}
- </a>
- </li>
- {/each}
- </ol>
+ {#if !disableActivityHistory}
+ <div class="categories-grid bottom-category" style="padding-top: 0;">
+ <div class="category-grid pure-category">
+ <div id="activity-history">
+ <ActivityHistoryGrid {user} activityData={activities} />
</div>
- {:else}
- Loading&nbsp;...
- {/if}
- </div>
- </div>
- </div>
- {#if !disableActivityHistory}
- <div class="categories-grid" style="padding-top: 0;">
- <div class="category-grid pure-category">
- <div id="activity-history">
- <ActivityHistoryGrid {user} />
</div>
</div>
- </div>
- {/if}
- {#if watermark}
- <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>
+ {/if}
+ {#if watermark}
+ <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>
- </div>
- {/if}
- </div>
-
- <p />
-
- <p>
- <a href={'#'} on:click={screenshot}>Generate image</a>
- </p>
-
- <details>
- <summary>Options</summary>
- <div id="options">
- <input type="checkbox" bind:checked={watermark} /> Enable 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={disableActivityHistory} /> Disable activity history<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 and OVAs<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 />
- <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 />
- <input
- type="text"
- bind:value={excludedKeywordsInput}
- on:keypress={(e) => {
- e.key === 'Enter' && submitExcludedKeywords();
- }}
- />
- Excluded keywords
- <a href={`#`} on:click={submitExcludedKeywords} title="Or click your Enter key">Submit</a>
- <br />
- <SettingHint>Comma separated list (e.g., "My Hero, Kaguya")</SettingHint>
+ {/if}
</div>
- </details>
- <p />
+ <p />
- <div id="wrapped-final" />
+ <p>
+ <a href={'#'} on:click={screenshot}>Generate image</a>
+ </p>
+
+ <details>
+ <summary>Options</summary>
+ <div id="options">
+ <input type="checkbox" bind:checked={watermark} /> Enable 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={disableActivityHistory} /> Disable activity history<br
+ />
+ {#if !disableActivityHistory}
+ <input type="checkbox" bind:checked={useFullActivityHistory} />
+ Enable full-year activity<br />
+ <SettingHint>
+ If you have many activities, this <b>will</b> cause heavy rate-limiting
+ </SettingHint><br />
+ {/if}
+
+ <p />
+
+ <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 and OVAs<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 />
+ <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 />
+ <input
+ type="text"
+ bind:value={excludedKeywordsInput}
+ on:keypress={(e) => {
+ e.key === 'Enter' && submitExcludedKeywords();
+ }}
+ />
+ Excluded keywords
+ <a href={`#`} on:click={submitExcludedKeywords} title="Or click your Enter key">Submit</a>
+ <br />
+ <SettingHint>Comma separated list (e.g., "My Hero, Kaguya")</SettingHint>
+ </div>
+ </details>
- {#if generated}
<p />
- <blockquote>
- Click on the image to download, or right click and select "Save Image As...".
- </blockquote>
- {/if}
+ <div id="wrapped-final" />
+
+ {#if generated}
+ <p />
+
+ <blockquote>
+ Click on the image to download, or right click and select "Save Image As...".
+ </blockquote>
+ {/if}
+ {:catch}
+ <Error />
+ {/await}
{:catch}
- <Error />
+ <Error type={'Full-year activity history'} loginSessionError={false} />
{/await}
{:else}
Loading&nbsp;...