diff options
Diffstat (limited to 'src/lib/Tools/Birthdays.svelte')
| -rw-r--r-- | src/lib/Tools/Birthdays.svelte | 139 |
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> |