aboutsummaryrefslogtreecommitdiff
path: root/src/lib/Tools/Birthdays.svelte
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Tools/Birthdays.svelte')
-rw-r--r--src/lib/Tools/Birthdays.svelte139
1 files changed, 139 insertions, 0 deletions
diff --git a/src/lib/Tools/Birthdays.svelte b/src/lib/Tools/Birthdays.svelte
new file mode 100644
index 00000000..d6d032c3
--- /dev/null
+++ b/src/lib/Tools/Birthdays.svelte
@@ -0,0 +1,139 @@
+<script lang="ts">
+ import { browser } from '$app/environment';
+ import { page } from '$app/stores';
+ import { ACDBBirthdays, type ACDBBirthday } from '$lib/Birthday/ACDB';
+ import { aniSearchBirthdays, type aniSearchBirthday } from '$lib/Birthday/aniSearch';
+ import Error from '$lib/Error.svelte';
+ import { onMount } from 'svelte';
+ import { clearAllParameters, parseOrDefault } from '../Utility/parameters';
+
+ interface Birthday {
+ name: string;
+ image: string;
+ origin?: string;
+ }
+
+ const urlParameters = browser ? new URLSearchParams(window.location.search) : null;
+ let date = new Date();
+ let month = parseOrDefault(urlParameters, 'month', date.getMonth() + 1);
+ let day = parseOrDefault(urlParameters, 'day', date.getDate());
+ let anisearchBirthdays: Promise<aniSearchBirthday[]>;
+ let acdbBirthdays: Promise<ACDBBirthday[]>;
+
+ $: {
+ month = Math.min(month, 12);
+ month = Math.max(month, 1);
+ day = Math.min(day, new Date(0, month, 0).getDate());
+ day = Math.max(day, 1);
+
+ anisearchBirthdays = aniSearchBirthdays(month, day);
+ acdbBirthdays = ACDBBirthdays(month, day);
+
+ if (browser) {
+ $page.url.searchParams.set('month', month.toString());
+ $page.url.searchParams.set('day', day.toString());
+ clearAllParameters(['month', 'day']);
+ history.replaceState(null, '', `?${$page.url.searchParams.toString()}`);
+ }
+ }
+
+ onMount(() => clearAllParameters(['month', 'day']));
+
+ const normalizeName = (name: string): string => name.toLowerCase().split(' ').sort().join(' ');
+
+ const fixName = (name: string): string => {
+ const split = name.split(' ');
+ const last = split[split.length - 1];
+
+ if (last === last.toUpperCase()) {
+ split[split.length - 1] = last[0] + last.slice(1).toLowerCase();
+ return split.join(' ');
+ }
+
+ return name;
+ };
+
+ const combineBirthdaySources = (
+ acdb: ACDBBirthday[],
+ aniSearch: aniSearchBirthday[]
+ ): Birthday[] => {
+ const nameMap = new Map<string, Birthday>();
+
+ for (const entry of aniSearch.map((entry) => ({
+ ...entry,
+ normalized_name: normalizeName(entry.name)
+ })))
+ nameMap.set(entry.normalized_name, {
+ name: fixName(entry.name),
+ image: entry.image
+ });
+
+ for (const entry of acdb) {
+ const normalized_name = normalizeName(entry.name);
+
+ if (!nameMap.has(normalized_name))
+ nameMap.set(normalized_name, {
+ name: entry.name,
+ image: entry.character_image,
+ origin: entry.origin
+ });
+ }
+
+ return Array.from(nameMap.values());
+ };
+</script>
+
+{#await acdbBirthdays}
+ <p>Loading 33% ...</p>
+{:then acdbBirthdays}
+ {#await anisearchBirthdays}
+ <p>Loading 66% ...</p>
+ {:then anisearch}
+ {@const birthdays = combineBirthdaySources(acdbBirthdays, anisearch)}
+
+ <p>
+ <select bind:value={month}>
+ {#each Array.from({ length: 12 }, (_, i) => i + 1) as month}
+ <option value={month}>
+ {new Date(0, month - 1).toLocaleString('default', { month: 'long' })}
+ </option>
+ {/each}
+ </select>
+
+ <select bind:value={day}>
+ {#each Array.from({ length: new Date(0, month, 0).getDate() }, (_, i) => i + 1) as day}
+ <option value={day}>{day}</option>
+ {/each}
+ </select>
+ </p>
+
+ <div id="characters">
+ {#each birthdays as birthday}
+ <div>
+ <a
+ href={`https://anilist.co/search/characters?search=${encodeURIComponent(
+ birthday.name
+ ).replace(/%20/g, '+')}`}
+ target="_blank"
+ >
+ {birthday.name}
+ <img src={birthday.image} alt="Character (Large)" class="character-image" />
+ </a>
+ </div>
+ {/each}
+ </div>
+ {:catch}
+ <Error type="Character" />
+ {/await}
+{:catch}
+ <Error type="Character" />
+{/await}
+
+<style>
+ #characters {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
+ grid-gap: 0.25%;
+ grid-row-gap: 1rem;
+ }
+</style>