diff options
| author | Fuwn <[email protected]> | 2024-10-09 00:41:20 -0700 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2024-10-09 00:41:43 -0700 |
| commit | 998b63a35256ac985a5a2714dd1ca451af4dfd8a (patch) | |
| tree | 50796121a9d5ab0330fdc5d7e098bda2860d9726 /src/routes/+layout.svelte | |
| parent | feat(graphql): add badgeCount field (diff) | |
| download | due.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.tar.xz due.moe-998b63a35256ac985a5a2714dd1ca451af4dfd8a.zip | |
chore(prettier): use spaces instead of tabs
Diffstat (limited to 'src/routes/+layout.svelte')
| -rw-r--r-- | src/routes/+layout.svelte | 636 |
1 files changed, 318 insertions, 318 deletions
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d9b257e9..6d7fe757 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,329 +1,329 @@ <script lang="ts"> - import type { SubsPleaseEpisode } from '$lib/Media/Anime/Airing/Subtitled/subsPlease'; - import { env } from '$env/dynamic/public'; - import { userIdentity as getUserIdentity } from '$lib/Data/AniList/identity'; - import { onDestroy, onMount } from 'svelte'; - import userIdentity from '$stores/identity'; - import settings from '$stores/settings'; - import { browser } from '$app/environment'; - import HeadTitle from '$lib/Home/HeadTitle.svelte'; - import '../app.css'; - import { readable, type Readable } from 'svelte/store'; - import { navigating } from '$app/stores'; - import Notifications from 'svelte-notifications'; - import * as EventNotification from '$lib/Notification/Notification.svelte'; - import Root from '$lib/Home/Root.svelte'; - import root from '$lib/Utility/root'; - import { addMessages, init, locale as i18nLocale, locales } from 'svelte-i18n'; - import english from '$lib/Locale/english'; - import japanese from '$lib/Locale/japanese'; - import type { LocaleDictionary } from '$lib/Locale/layout'; - import locale from '$stores/locale'; - import Skeleton from '$lib/Loading/Skeleton.svelte'; - import subsPlease from '$stores/subsPlease'; - import Dropdown from '$lib/Layout/Dropdown.svelte'; - import { injectSpeedInsights } from '@vercel/speed-insights/sveltekit'; - import subtitles from '$lib/Data/Static/subtitles.json'; - import settingsSyncPulled from '$stores/settingsSyncPulled'; - import settingsSyncTimes from '$stores/settingsSyncTimes'; - import Announcement from '$lib/Announcement.svelte'; - import Message from '$lib/Loading/Message.svelte'; - import { requestNotifications } from '$lib/Utility/notifications'; - import { database as userDatabase } from '$lib/Database/IDB/user'; - - injectSpeedInsights(); - - export let data; - - let isHeaderVisible = true; - let previousScrollPosition = 0; - let notificationInterval: NodeJS.Timeout | undefined = undefined; - - addMessages('en', english as unknown as LocaleDictionary); - addMessages('ja', japanese as unknown as LocaleDictionary); - init({ fallbackLocale: 'en', initialLocale: $settings.displayLanguage }); - - $: i18nLocale.set($settings.displayLanguage); - - const navigationOrder = ['/', '/completed', '/schedule', '/updates', '/tools', '/settings']; - const previousPage: Readable<string | null> = readable(null, (set) => { - const unsubscribe = navigating.subscribe(($navigating) => { - if ($navigating && $navigating.from) set($navigating.from.url.pathname as unknown as null); - }); - - return () => unsubscribe(); - }); - - $: way = data.url.includes('/user') - ? 200 - : $previousPage && $previousPage.includes('/user') - ? -200 - : navigationOrder.indexOf(data.url) > navigationOrder.indexOf($previousPage ?? '/') - ? 200 - : -200; - - const handleScroll = () => { - const currentScrollPosition = window.scrollY; - - isHeaderVisible = - currentScrollPosition <= 100 || currentScrollPosition < previousScrollPosition; - previousScrollPosition = currentScrollPosition; - }; - - onMount(async () => { - if (browser) { - if (localStorage.getItem('redirect')) { - window.location.href = localStorage.getItem('redirect') ?? '/'; - - localStorage.removeItem('redirect'); - } - - window.addEventListener('scroll', handleScroll); - - if (localStorage.getItem('commit') !== data.commit) { - localStorage.removeItem('identity'); - localStorage.removeItem('anime'); - localStorage.removeItem('manga'); - localStorage.removeItem('lastPruneTimes'); - localStorage.setItem('commit', data.commit); - } - } - - settings.get(); - - if (data.user !== undefined && $userIdentity.id === -2) - getUserIdentity(data.user).then((h) => userIdentity.set(h)); - - if ($settings.settingsSync && $userIdentity.id !== -2) - fetch(root(`/api/configuration?id=${$userIdentity.id}`)).then((response) => { - if (response.ok) - response.json().then((data) => { - if (data && data.configuration) { - console.log('Pulled remote configuration'); - settings.set(data.configuration); - settingsSyncPulled.set(true); - settingsSyncTimes.set({ - lastPull: new Date(), - lastPush: new Date(data.updated_at + 'Z') - }); - } - }); - }); - - if (!(await userDatabase.users.get($userIdentity.id))) - userDatabase.users.put({ - id: $userIdentity.id, - user: data.user, - lastNotificationID: null - }); - - if ($settings.displayAniListNotifications && data.user !== undefined) - if ('Notification' in window && navigator.serviceWorker) requestNotifications(); - }); - - onDestroy(() => { - if (browser) window.removeEventListener('scroll', handleScroll); - - if (notificationInterval) clearInterval(notificationInterval); - }); - - $: { - if ((data.url === '/' || data.url === '/completed' || data.url === '/schedule') && !$subsPlease) - fetch(root(`/api/subsplease?tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}`)) - .then((r) => r.json()) - .then((r) => { - for (const day in subtitles) { - if (!r.schedule[day]) r.schedule[day] = []; - - (subtitles[day as keyof typeof subtitles] as SubsPleaseEpisode[]).forEach((episode) => { - r.schedule[day].push({ - title: episode.title, - page: episode.page || '', - image_url: episode.image_url || '', - time: episode.time - }); - }); - } - - subsPlease.set(r); - }); - } + import type { SubsPleaseEpisode } from '$lib/Media/Anime/Airing/Subtitled/subsPlease'; + import { env } from '$env/dynamic/public'; + import { userIdentity as getUserIdentity } from '$lib/Data/AniList/identity'; + import { onDestroy, onMount } from 'svelte'; + import userIdentity from '$stores/identity'; + import settings from '$stores/settings'; + import { browser } from '$app/environment'; + import HeadTitle from '$lib/Home/HeadTitle.svelte'; + import '../app.css'; + import { readable, type Readable } from 'svelte/store'; + import { navigating } from '$app/stores'; + import Notifications from 'svelte-notifications'; + import * as EventNotification from '$lib/Notification/Notification.svelte'; + import Root from '$lib/Home/Root.svelte'; + import root from '$lib/Utility/root'; + import { addMessages, init, locale as i18nLocale, locales } from 'svelte-i18n'; + import english from '$lib/Locale/english'; + import japanese from '$lib/Locale/japanese'; + import type { LocaleDictionary } from '$lib/Locale/layout'; + import locale from '$stores/locale'; + import Skeleton from '$lib/Loading/Skeleton.svelte'; + import subsPlease from '$stores/subsPlease'; + import Dropdown from '$lib/Layout/Dropdown.svelte'; + import { injectSpeedInsights } from '@vercel/speed-insights/sveltekit'; + import subtitles from '$lib/Data/Static/subtitles.json'; + import settingsSyncPulled from '$stores/settingsSyncPulled'; + import settingsSyncTimes from '$stores/settingsSyncTimes'; + import Announcement from '$lib/Announcement.svelte'; + import Message from '$lib/Loading/Message.svelte'; + import { requestNotifications } from '$lib/Utility/notifications'; + import { database as userDatabase } from '$lib/Database/IDB/user'; + + injectSpeedInsights(); + + export let data; + + let isHeaderVisible = true; + let previousScrollPosition = 0; + let notificationInterval: NodeJS.Timeout | undefined = undefined; + + addMessages('en', english as unknown as LocaleDictionary); + addMessages('ja', japanese as unknown as LocaleDictionary); + init({ fallbackLocale: 'en', initialLocale: $settings.displayLanguage }); + + $: i18nLocale.set($settings.displayLanguage); + + const navigationOrder = ['/', '/completed', '/schedule', '/updates', '/tools', '/settings']; + const previousPage: Readable<string | null> = readable(null, (set) => { + const unsubscribe = navigating.subscribe(($navigating) => { + if ($navigating && $navigating.from) set($navigating.from.url.pathname as unknown as null); + }); + + return () => unsubscribe(); + }); + + $: way = data.url.includes('/user') + ? 200 + : $previousPage && $previousPage.includes('/user') + ? -200 + : navigationOrder.indexOf(data.url) > navigationOrder.indexOf($previousPage ?? '/') + ? 200 + : -200; + + const handleScroll = () => { + const currentScrollPosition = window.scrollY; + + isHeaderVisible = + currentScrollPosition <= 100 || currentScrollPosition < previousScrollPosition; + previousScrollPosition = currentScrollPosition; + }; + + onMount(async () => { + if (browser) { + if (localStorage.getItem('redirect')) { + window.location.href = localStorage.getItem('redirect') ?? '/'; + + localStorage.removeItem('redirect'); + } + + window.addEventListener('scroll', handleScroll); + + if (localStorage.getItem('commit') !== data.commit) { + localStorage.removeItem('identity'); + localStorage.removeItem('anime'); + localStorage.removeItem('manga'); + localStorage.removeItem('lastPruneTimes'); + localStorage.setItem('commit', data.commit); + } + } + + settings.get(); + + if (data.user !== undefined && $userIdentity.id === -2) + getUserIdentity(data.user).then((h) => userIdentity.set(h)); + + if ($settings.settingsSync && $userIdentity.id !== -2) + fetch(root(`/api/configuration?id=${$userIdentity.id}`)).then((response) => { + if (response.ok) + response.json().then((data) => { + if (data && data.configuration) { + console.log('Pulled remote configuration'); + settings.set(data.configuration); + settingsSyncPulled.set(true); + settingsSyncTimes.set({ + lastPull: new Date(), + lastPush: new Date(data.updated_at + 'Z') + }); + } + }); + }); + + if (!(await userDatabase.users.get($userIdentity.id))) + userDatabase.users.put({ + id: $userIdentity.id, + user: data.user, + lastNotificationID: null + }); + + if ($settings.displayAniListNotifications && data.user !== undefined) + if ('Notification' in window && navigator.serviceWorker) requestNotifications(); + }); + + onDestroy(() => { + if (browser) window.removeEventListener('scroll', handleScroll); + + if (notificationInterval) clearInterval(notificationInterval); + }); + + $: { + if ((data.url === '/' || data.url === '/completed' || data.url === '/schedule') && !$subsPlease) + fetch(root(`/api/subsplease?tz=${Intl.DateTimeFormat().resolvedOptions().timeZone}`)) + .then((r) => r.json()) + .then((r) => { + for (const day in subtitles) { + if (!r.schedule[day]) r.schedule[day] = []; + + (subtitles[day as keyof typeof subtitles] as SubsPleaseEpisode[]).forEach((episode) => { + r.schedule[day].push({ + title: episode.title, + page: episode.page || '', + image_url: episode.image_url || '', + time: episode.time + }); + }); + } + + subsPlease.set(r); + }); + } </script> <HeadTitle /> {#if !$locales.includes('en') || !$locale().navigation} - <Message /> - - {#if data.url === '/settings'} - <Skeleton grid={true} count={1} height="10vh" /> - <Skeleton grid={true} count={1} height="30vh" /> - <Skeleton grid={true} count={1} height="30vh" /> - {:else} - <Skeleton grid={true} count={1} height="10vh" /> - <Skeleton grid={true} count={1} height="80vh" /> - {/if} + <Message /> + + {#if data.url === '/settings'} + <Skeleton grid={true} count={1} height="10vh" /> + <Skeleton grid={true} count={1} height="30vh" /> + <Skeleton grid={true} count={1} height="30vh" /> + {:else} + <Skeleton grid={true} count={1} height="10vh" /> + <Skeleton grid={true} count={1} height="80vh" /> + {/if} {:else} - <Announcement /> - - <div class="container"> - <div class="card card-centered header" class:header-hidden={!isHeaderVisible}> - <div> - <a href={root('/')} class="header-item">{$locale().navigation.home}</a><a - href={root('/completed')} - class="header-item" - > - {$locale().navigation.completed} - </a> - <Dropdown - items={[ - { name: $locale().navigation.subtitleSchedule, url: root('/schedule') }, - { name: $locale().navigation.hololive, url: root('/hololive') }, - { name: $locale().tools.tool.characterBirthdays.short, url: root('/birthdays') }, - { name: $locale().navigation.newReleases, url: root('/updates') } - ]} - header={false} - > - <span slot="title" class="header-item">{$locale().navigation.schedule}</span> - </Dropdown> - <a href={root('/tools')} class="header-item">{$locale().navigation.tools}</a> - <a href={root('/settings')} class="header-item">{$locale().navigation.settings}</a> - - <span class="header-item opaque separator">•</span> - - {#if data.user} - <Dropdown - items={[ - { name: $locale().navigation.myProfile, url: root(`/user/${$userIdentity.name}`) }, - { - name: $locale().navigation.myBadgeWall, - url: root(`/user/${$userIdentity.name}/badges`) - }, - { - name: $locale().navigation.logOut, - url: '#', - preventDefault: true, - onClick: () => { - localStorage.removeItem('identity'); - localStorage.removeItem('commit'); - - document.cookie = 'user=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; - - window.location.href = root('/api/authentication/log-out'); - } - } - ]} - header={false} - > - <span slot="title" class="header-item"> - {$locale().navigation.profile} - </span> - </Dropdown> - {/if} - - {#if data.user === undefined} - <a - class="header-item" - href={`https://anilist.co/api/v2/oauth/authorize?client_id=${env.PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${env.PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`} - on:click={() => { - localStorage.setItem( - 'redirect', - window.location.origin + window.location.pathname + window.location.search - ); - }} - > - {$locale().navigation.logIn} - </a> - {:else if data.user} - <a href={root(`/user/${$userIdentity.name}`)} class="header-item"> - <img class="avatar" src={$userIdentity.avatar} alt="Avatar" /> - </a> - {/if} - </div> - </div> - - <p /> - - <Notifications item={EventNotification} zIndex={5000}> - <Root {data} {way}> - {#if $userIdentity.id !== -1} - <slot /> - {:else if data.url === '/settings'} - <Skeleton grid={true} count={1} height="10vh" /> - <Skeleton grid={true} count={1} height="30vh" /> - <Skeleton grid={true} count={1} height="30vh" /> - {:else} - <Skeleton grid={true} count={1} height="10vh" /> - <Skeleton grid={true} count={1} height="80vh" /> - {/if} - </Root> - </Notifications> - </div> + <Announcement /> + + <div class="container"> + <div class="card card-centered header" class:header-hidden={!isHeaderVisible}> + <div> + <a href={root('/')} class="header-item">{$locale().navigation.home}</a><a + href={root('/completed')} + class="header-item" + > + {$locale().navigation.completed} + </a> + <Dropdown + items={[ + { name: $locale().navigation.subtitleSchedule, url: root('/schedule') }, + { name: $locale().navigation.hololive, url: root('/hololive') }, + { name: $locale().tools.tool.characterBirthdays.short, url: root('/birthdays') }, + { name: $locale().navigation.newReleases, url: root('/updates') } + ]} + header={false} + > + <span slot="title" class="header-item">{$locale().navigation.schedule}</span> + </Dropdown> + <a href={root('/tools')} class="header-item">{$locale().navigation.tools}</a> + <a href={root('/settings')} class="header-item">{$locale().navigation.settings}</a> + + <span class="header-item opaque separator">•</span> + + {#if data.user} + <Dropdown + items={[ + { name: $locale().navigation.myProfile, url: root(`/user/${$userIdentity.name}`) }, + { + name: $locale().navigation.myBadgeWall, + url: root(`/user/${$userIdentity.name}/badges`) + }, + { + name: $locale().navigation.logOut, + url: '#', + preventDefault: true, + onClick: () => { + localStorage.removeItem('identity'); + localStorage.removeItem('commit'); + + document.cookie = 'user=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'; + + window.location.href = root('/api/authentication/log-out'); + } + } + ]} + header={false} + > + <span slot="title" class="header-item"> + {$locale().navigation.profile} + </span> + </Dropdown> + {/if} + + {#if data.user === undefined} + <a + class="header-item" + href={`https://anilist.co/api/v2/oauth/authorize?client_id=${env.PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${env.PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`} + on:click={() => { + localStorage.setItem( + 'redirect', + window.location.origin + window.location.pathname + window.location.search + ); + }} + > + {$locale().navigation.logIn} + </a> + {:else if data.user} + <a href={root(`/user/${$userIdentity.name}`)} class="header-item"> + <img class="avatar" src={$userIdentity.avatar} alt="Avatar" /> + </a> + {/if} + </div> + </div> + + <p /> + + <Notifications item={EventNotification} zIndex={5000}> + <Root {data} {way}> + {#if $userIdentity.id !== -1} + <slot /> + {:else if data.url === '/settings'} + <Skeleton grid={true} count={1} height="10vh" /> + <Skeleton grid={true} count={1} height="30vh" /> + <Skeleton grid={true} count={1} height="30vh" /> + {:else} + <Skeleton grid={true} count={1} height="10vh" /> + <Skeleton grid={true} count={1} height="80vh" /> + {/if} + </Root> + </Notifications> + </div> {/if} <style lang="scss"> - .header { - font-family: 'DM Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, - Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; - font-size: 1.05em; - font-weight: 600; - padding: 0.8rem 0.4rem; - z-index: 4; - position: sticky; - top: 1.25rem; - transition: transform 0.3s ease; - } - - .header-hidden { - transform: translateY(-150%); - } - - :global(html.light-theme) { - filter: invert(0); - } - - :global(html.light-theme img) { - filter: invert(0); - } - - .container { - height: 100%; - display: flex; - flex-direction: column; - } - - :global(a) { - text-decoration: none; - transition: all 0.15s ease-in-out; - } - - :global(a:hover) { - text-decoration: underline; - } - - .header-item { - margin: 0 0.5rem; - color: var(--base06); - white-space: nowrap; - } - - .header-item:hover { - text-decoration: none; - } - - .header-item:active { - outline: none; - } - - .avatar { - width: 2em; - display: inline-block; - vertical-align: middle; - border-radius: 8px; - box-shadow: 0 1.5px 9px var(--base01), 0 0 0 4px var(--base0E), 0 4px 30px var(--base01); - } - - .separator { - color: var(--base04); - } + .header { + font-family: 'DM Sans', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + font-size: 1.05em; + font-weight: 600; + padding: 0.8rem 0.4rem; + z-index: 4; + position: sticky; + top: 1.25rem; + transition: transform 0.3s ease; + } + + .header-hidden { + transform: translateY(-150%); + } + + :global(html.light-theme) { + filter: invert(0); + } + + :global(html.light-theme img) { + filter: invert(0); + } + + .container { + height: 100%; + display: flex; + flex-direction: column; + } + + :global(a) { + text-decoration: none; + transition: all 0.15s ease-in-out; + } + + :global(a:hover) { + text-decoration: underline; + } + + .header-item { + margin: 0 0.5rem; + color: var(--base06); + white-space: nowrap; + } + + .header-item:hover { + text-decoration: none; + } + + .header-item:active { + outline: none; + } + + .avatar { + width: 2em; + display: inline-block; + vertical-align: middle; + border-radius: 8px; + box-shadow: 0 1.5px 9px var(--base01), 0 0 0 4px var(--base0E), 0 4px 30px var(--base01); + } + + .separator { + color: var(--base04); + } </style> |