diff options
Diffstat (limited to 'src/lib')
92 files changed, 1429 insertions, 822 deletions
diff --git a/src/lib/Announcement.svelte b/src/lib/Announcement.svelte index 7bcc9df7..0dc330c7 100644 --- a/src/lib/Announcement.svelte +++ b/src/lib/Announcement.svelte @@ -40,9 +40,9 @@ {line}<br /> {/each} - <p /> + <p></p> - <button on:click={dismiss} class="dismiss">{dismissButton || 'Dismiss'}</button> + <button onclick={dismiss} class="dismiss">{dismissButton || 'Dismiss'}</button> </Popup> {/if} diff --git a/src/lib/Error/AnimeRateLimited.svelte b/src/lib/Error/AnimeRateLimited.svelte index 96df3ad5..4671d45b 100644 --- a/src/lib/Error/AnimeRateLimited.svelte +++ b/src/lib/Error/AnimeRateLimited.svelte @@ -1,15 +1,22 @@ <script> import Popup from '$lib/Layout/Popup.svelte'; + /** + * @typedef {Object} Props + * @property {import('svelte').Snippet} [children] + */ + + /** @type {Props} */ + let { children } = $props(); </script> <Popup locked fullscreen> - <p><slot /></p> + <p>{@render children?.()}</p> <span>It is likely that you have been rate-limited by AniList. Please try again later.</span> {#await fetch('https://api.waifu.pics/sfw/cry') then response} {#await response.json() then json} - <p /> + <p></p> <a href={`https://trace.moe/?url=${encodeURIComponent(json.url)}`} target="_blank"> <img src={json.url} alt="" style="width: 30vw;" /> diff --git a/src/lib/Error/DotDotDot.svelte b/src/lib/Error/DotDotDot.svelte index 73261eba..22cd895c 100644 --- a/src/lib/Error/DotDotDot.svelte +++ b/src/lib/Error/DotDotDot.svelte @@ -1,11 +1,15 @@ <script lang="ts"> import { onDestroy, onMount } from 'svelte'; - export let max: number | undefined = undefined; - export let perMs = 1000; - export let start = ''; + interface Props { + max?: number | undefined; + perMs?: number; + start?: string; + } - let dots = start; + let { max = undefined, perMs = 1000, start = '' }: Props = $props(); + + let dots = $state(start); let interval: NodeJS.Timeout; onMount(() => { diff --git a/src/lib/Error/LogInRestricted.svelte b/src/lib/Error/LogInRestricted.svelte index 07a9adec..d19dfd3d 100644 --- a/src/lib/Error/LogInRestricted.svelte +++ b/src/lib/Error/LogInRestricted.svelte @@ -7,7 +7,7 @@ <div class="message"> Please <a 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={() => { + onclick={() => { localStorage.setItem( 'redirect', window.location.origin + window.location.pathname + window.location.search diff --git a/src/lib/Error/Notice.svelte b/src/lib/Error/Notice.svelte index d4cf96c5..6bdd7327 100644 --- a/src/lib/Error/Notice.svelte +++ b/src/lib/Error/Notice.svelte @@ -1,2 +1,12 @@ +<script> + /** + * @typedef {Object} Props + * @property {import('svelte').Snippet} [children] + */ + + /** @type {Props} */ + let { children } = $props(); +</script> + <b>Notice:</b> -<slot /> +{@render children?.()} diff --git a/src/lib/Error/RateLimited.svelte b/src/lib/Error/RateLimited.svelte index 973d75d9..15e779b3 100644 --- a/src/lib/Error/RateLimited.svelte +++ b/src/lib/Error/RateLimited.svelte @@ -1,10 +1,23 @@ <script lang="ts"> - export let type = 'Media'; - export let loginSessionError = true; - export let contact = true; - export let list = true; - export let card = false; - export let might = true; + interface Props { + type?: string; + loginSessionError?: boolean; + contact?: boolean; + list?: boolean; + card?: boolean; + might?: boolean; + children?: import('svelte').Snippet; + } + + let { + type = 'Media', + loginSessionError = true, + contact = true, + list = true, + card = false, + might = true, + children + }: Props = $props(); </script> <div class:card> @@ -24,10 +37,10 @@ </p> {/if} - <slot /> + {@render children?.()} {#if contact} - <p /> + <p></p> If the problem persists, please contact <a href="https://anilist.co/user/fuwn" target="_blank">@fuwn</a> on AniList. @@ -48,10 +61,10 @@ </p> {/if} - <slot /> + {@render children?.()} {#if contact} - <p /> + <p></p> If the problem persists, please contact <a href="https://anilist.co/user/fuwn" target="_blank">@fuwn</a> on AniList. diff --git a/src/lib/Events/Event.svelte b/src/lib/Events/Event.svelte index a2b1d2b5..f0a4f9f7 100644 --- a/src/lib/Events/Event.svelte +++ b/src/lib/Events/Event.svelte @@ -7,39 +7,39 @@ export let avatar = false; </script> -<a href={event.anilist_url} target="_blank"> - <div - class="card" - id="user-grid" - style={`background-image: ${event.banner ? `url(${event.banner})` : 'none'}; padding: 0;`} - > - {#if event} +<div + class="card" + id="user-grid" + style={`background-image: ${event.banner ? `url(${event.banner})` : 'none'}; padding: 0;`} +> + {#if event} + <a href={event.anilist_url} target="_blank"> <img src={event.banner} alt="" id="cover-image" /> - {/if} + </a> + {/if} - <div class="card" id="user-grid-content"> - {#if avatar} - <div id="user-grid-avatar"> - <a href={root(`/events/group/${event.group.anilist_username}`)}> - <img src={event.group.avatar} alt="" width="100vw" id="avatar" /> - </a> - </div> - {/if} + <div class="card" id="user-grid-content"> + {#if avatar} + <div id="user-grid-avatar"> + <a href={root(`/events/group/${event.group.anilist_username}`)}> + <img src={event.group.avatar} alt="" width="100vw" id="avatar" /> + </a> + </div> + {/if} - <div id="user-grid-content-text"> - <p> - <a href={event.anilist_url} target="_blank" class="title-text"> - {event.title} - </a> - <br /> - {$locale().dateFormatter(new Date(event.created_at))} - </p> + <div id="user-grid-content-text"> + <p> + <a href={event.anilist_url} target="_blank" class="title-text"> + {event.title} + </a> + <br /> + {$locale().dateFormatter(new Date(event.created_at))} + </p> - <p>{event.description}</p> - </div> + <p>{event.description}</p> </div> </div> -</a> +</div> <style> #user-grid-content { diff --git a/src/lib/Events/Group.svelte b/src/lib/Events/Group.svelte index a3d24807..e88d9b2a 100644 --- a/src/lib/Events/Group.svelte +++ b/src/lib/Events/Group.svelte @@ -2,7 +2,11 @@ import type { Group } from '$lib/Database/SB/groups'; import tooltip from '$lib/Tooltip/tooltip'; - export let group: Group; + interface Props { + group: Group; + } + + let { group }: Props = $props(); </script> <div diff --git a/src/lib/Hololive/Lives.svelte b/src/lib/Hololive/Lives.svelte index 9e762df9..0f566df4 100644 --- a/src/lib/Hololive/Lives.svelte +++ b/src/lib/Hololive/Lives.svelte @@ -4,10 +4,19 @@ import type { Live, ParseResult } from './hololive'; import Stream from './Stream.svelte'; - export let schedule: ParseResult; - export let pinnedStreams: string[]; - export let getPinnedStreams: () => void; - export let filter: string | undefined; + interface Props { + schedule: ParseResult; + pinnedStreams: string[]; + getPinnedStreams: () => void; + filter: string | undefined; + } + + let { + schedule, + pinnedStreams, + getPinnedStreams, + filter + }: Props = $props(); const pinStream = (streamer: string) => fetch(root(`/api/preferences/pin?stream=${encodeURIComponent(streamer)}`), { @@ -17,7 +26,7 @@ } }).then(getPinnedStreams); - $: categorisedStreams = schedule.lives + let categorisedStreams = $derived(schedule.lives .filter((live) => (filter ? live.streamer === filter : true)) .sort((a, b) => new Date(a.time).getTime() - new Date(b.time).getTime()) .sort((a, b) => { @@ -55,7 +64,7 @@ return acc; }, { live: [], upcoming: [], ended: [] } - ); + )); </script> {#if schedule.lives.length === 0} @@ -68,7 +77,7 @@ {/each} </div> -<p /> +<p></p> <div class="container"> {#each categorisedStreams.upcoming as live} @@ -76,7 +85,7 @@ {/each} </div> -<p /> +<p></p> <div class="container"> {#each categorisedStreams.ended as live} diff --git a/src/lib/Hololive/Stream.svelte b/src/lib/Hololive/Stream.svelte index 12681acb..9f2b5e7b 100644 --- a/src/lib/Hololive/Stream.svelte +++ b/src/lib/Hololive/Stream.svelte @@ -5,10 +5,19 @@ import locale from '$stores/locale'; import Icon from '@iconify/svelte'; - export let live: any; - export let pinStream: (streamer: string) => void; - export let pinnedStreams: string[]; - export let icon: string; + interface Props { + live: any; + pinStream: (streamer: string) => void; + pinnedStreams: string[]; + icon: string; + } + + let { + live, + pinStream, + pinnedStreams, + icon + }: Props = $props(); </script> <div class="stream card"> @@ -16,7 +25,7 @@ <div class="pin-icon"> <a href={'#'} - on:click={(e) => { + onclick={(e) => { e.preventDefault(); pinStream(live.streamer); }} diff --git a/src/lib/Home/HeadTitle.svelte b/src/lib/Home/HeadTitle.svelte index 39a65ccd..a87da69a 100644 --- a/src/lib/Home/HeadTitle.svelte +++ b/src/lib/Home/HeadTitle.svelte @@ -1,6 +1,10 @@ <script lang="ts"> - export let route: string | undefined = undefined; - export let path = '/'; + interface Props { + route?: string | undefined; + path?: string; + } + + let { route = undefined, path = '/' }: Props = $props(); const title = (route ? `${route} • ` : '') + 'due.moe'; </script> diff --git a/src/lib/Home/LastActivity.svelte b/src/lib/Home/LastActivity.svelte index 000c7f7b..cdbf4d44 100644 --- a/src/lib/Home/LastActivity.svelte +++ b/src/lib/Home/LastActivity.svelte @@ -5,9 +5,13 @@ import { lastActivityDate } from '../Data/AniList/activity'; import settings from '$stores/settings'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } - let lastActivityWasToday = true; + let { user }: Props = $props(); + + let lastActivityWasToday = $state(true); onMount(async () => { if (user !== undefined && !$settings.displayDisableLastActivityWarning) { diff --git a/src/lib/Home/Root.svelte b/src/lib/Home/Root.svelte index bc1bdea9..e17f9a77 100644 --- a/src/lib/Home/Root.svelte +++ b/src/lib/Home/Root.svelte @@ -2,14 +2,19 @@ import settings from '$stores/settings'; import { fly } from 'svelte/transition'; - export let data: any; - export let way: number; + interface Props { + data: any; + way: number; + children?: import('svelte').Snippet; + } + + let { data, way, children }: Props = $props(); const animationDelay = 100; </script> {#if $settings.displayDisableAnimations} - <slot /> + {@render children?.()} {:else} {#key data.url} <div @@ -20,7 +25,7 @@ }} out:fly={{ x: -way, duration: animationDelay }} > - <slot /> + {@render children?.()} </div> {/key} {/if} diff --git a/src/lib/Image/FallbackImage.svelte b/src/lib/Image/FallbackImage.svelte index 2029cf0e..1eafdd4c 100644 --- a/src/lib/Image/FallbackImage.svelte +++ b/src/lib/Image/FallbackImage.svelte @@ -1,14 +1,27 @@ <script lang="ts"> - export let source: string | undefined | null; - export let alternative: string | undefined | null; - export let fallback: string | undefined | null; - export let maxReplaceCount = 1; - export let replaceDelay = 1000; - export let error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg'; - export let hideOnError = false; - export let style = ''; + interface Props { + source: string | undefined | null; + alternative: string | undefined | null; + fallback: string | undefined | null; + maxReplaceCount?: number; + replaceDelay?: number; + error?: string; + hideOnError?: boolean; + style?: string; + } + + let { + source, + alternative, + fallback, + maxReplaceCount = 1, + replaceDelay = 1000, + error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg', + hideOnError = false, + style = '' + }: Props = $props(); - let replaceCount = 0; + let replaceCount = $state(0); const delayedReplace = (event: Event, image: string | undefined | null) => { if (replaceCount >= maxReplaceCount) return; @@ -27,7 +40,7 @@ alt={alternative} loading="lazy" class="badge" - on:error={(e) => delayedReplace(e, fallback)} + onerror={(e) => delayedReplace(e, fallback)} {style} /> {:else if !hideOnError} diff --git a/src/lib/Image/ParallaxImage.svelte b/src/lib/Image/ParallaxImage.svelte index 48565ce1..390b9c92 100644 --- a/src/lib/Image/ParallaxImage.svelte +++ b/src/lib/Image/ParallaxImage.svelte @@ -2,17 +2,31 @@ import { cubicOut } from 'svelte/easing'; import { tweened } from 'svelte/motion'; - export let source: string; - export let duration = 300 * 1.75; - export let easing = cubicOut; - export let alternativeText: string; - export let classList = ''; - export let style = ''; - export let factor = 1.25; - export let limit = 300 * 1.75; - export let borderRadius = '8px'; - - let badgeReference: HTMLImageElement; + interface Props { + source: string; + duration?: any; + easing?: any; + alternativeText: string; + classList?: string; + style?: string; + factor?: number; + limit?: any; + borderRadius?: string; + } + + let { + source, + duration = 300 * 1.75, + easing = cubicOut, + alternativeText, + classList = '', + style = '', + factor = 1.25, + limit = 300 * 1.75, + borderRadius = '8px' + }: Props = $props(); + + let badgeReference: HTMLImageElement = $state(); const mouse = tweened({ x: 0, y: 0 }, { duration, easing }); const handleMouseMove = (event: MouseEvent) => { @@ -40,7 +54,7 @@ -$mouse.x / 10 }deg); border-radius: ${borderRadius}; ${style}`} alt={alternativeText} - on:mousemove={handleMouseMove} - on:mouseleave={handleMouseLeave} + onmousemove={handleMouseMove} + onmouseleave={handleMouseLeave} class={classList} /> diff --git a/src/lib/Landing.svelte b/src/lib/Landing.svelte index 29a341f1..bfe69de9 100644 --- a/src/lib/Landing.svelte +++ b/src/lib/Landing.svelte @@ -39,7 +39,7 @@ </div> </div> -<p /> +<p></p> <div class="example-item card"> <div class="card item-description"> @@ -65,7 +65,7 @@ </div> </div> -<p /> +<p></p> <div class="example-item card"> <div class="item-content"> @@ -105,7 +105,7 @@ <span class="big-text"> <a 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={() => { + onclick={() => { localStorage.setItem( 'redirect', window.location.origin + window.location.pathname + window.location.search diff --git a/src/lib/Layout/Dropdown.svelte b/src/lib/Layout/Dropdown.svelte index fb270dfe..aca94975 100644 --- a/src/lib/Layout/Dropdown.svelte +++ b/src/lib/Layout/Dropdown.svelte @@ -6,12 +6,14 @@ preventDefault?: boolean; } - export let items: Item[] = []; - export let title: string | undefined = undefined; - export let header = true; - export let center = false; + let { + items = [] as Item[], + title = undefined as string | undefined, + header = true, + center = false + } = $props(); - let open = false; + let open = $state(false); const handleClickOutside = (event: any) => { if (!event.target.closest('.dropdown')) open = false; @@ -30,8 +32,8 @@ <span class={`${header ? 'header-item' : ''} dropdown-toggle`} id="dropdown-toggle" - on:click|preventDefault={() => (open = !open)} - on:keydown={() => {}} + onclick={() => (open = !open)} + onkeydown={() => {}} role="button" tabindex="0" > @@ -47,7 +49,7 @@ <a href={item.url} class="header-item" - on:click={(e) => { + onclick={(e) => { if (item.preventDefault) e.preventDefault(); if (item.onClick) item.onClick(); }} @@ -89,7 +91,10 @@ transform: translateY(-20px); visibility: hidden; $delay: 0.25s; - transition: opacity $delay ease, transform $delay ease, visibility 0s linear $delay; + transition: + opacity $delay ease, + transform $delay ease, + visibility 0s linear $delay; left: var(--dropdown-left); transform: var(--dropdown-transform); z-index: 1; diff --git a/src/lib/Layout/NumberTicker.svelte b/src/lib/Layout/NumberTicker.svelte index b5e2f49c..3bd7e6c8 100644 --- a/src/lib/Layout/NumberTicker.svelte +++ b/src/lib/Layout/NumberTicker.svelte @@ -1,11 +1,24 @@ <script> + import { run } from 'svelte/legacy'; + import { tweened } from 'svelte/motion'; import { cubicOut } from 'svelte/easing'; - export let start = 0; - export let end = 100; - export let duration = 2000; - export let delay = 0; + /** + * @typedef {Object} Props + * @property {number} [start] + * @property {number} [end] + * @property {number} [duration] + * @property {number} [delay] + */ + + /** @type {Props} */ + let { + start = 0, + end = 100, + duration = 2000, + delay = 0 + } = $props(); const count = tweened(start, { duration: duration, @@ -13,9 +26,9 @@ delay: delay }); - $: { + run(() => { count.set(end); - } + }); </script> <span class="counter" class:visible={$count !== start}> diff --git a/src/lib/Layout/Popup.svelte b/src/lib/Layout/Popup.svelte index b82e86c2..45b719f8 100644 --- a/src/lib/Layout/Popup.svelte +++ b/src/lib/Layout/Popup.svelte @@ -1,16 +1,32 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { browser } from '$app/environment'; import { onMount } from 'svelte'; - export let onLeave = () => { + interface Props { + onLeave?: any; + card?: boolean; + smallCard?: boolean; + fullscreen?: boolean; + show?: boolean; + locked?: boolean; + center?: boolean; + children?: import('svelte').Snippet; + } + + let { + onLeave = () => { return; - }; - export let card = true; - export let smallCard = false; - export let fullscreen = false; - export let show = true; - export let locked = false; - export let center = false; + }, + card = true, + smallCard = false, + fullscreen = false, + show = $bindable(true), + locked = false, + center = false, + children + }: Props = $props(); const handleClickOutside = (event: any) => { if (!locked && event.target.classList.contains('popup')) { @@ -24,24 +40,24 @@ if (browser) document.body.style.overflow = 'auto'; }); - $: { + run(() => { if (browser) { document.body.style.overflow = 'auto'; if (show) document.body.style.overflow = 'hidden'; else document.body.style.overflow = 'auto'; } - } + }); </script> -<svelte:window on:click={handleClickOutside} /> +<svelte:window onclick={handleClickOutside} /> {#if show} <div class={`popup ${fullscreen ? 'popup-fullscreen' : ''}`}> <span class={`${card ? `card ${smallCard ? 'card-small' : ''}` : ''} ${center ? 'centered' : ''}`} > - <slot /> + {@render children?.()} </span> </div> {/if} diff --git a/src/lib/Layout/TextTransition.svelte b/src/lib/Layout/TextTransition.svelte index 35cfe1ea..5f5bd036 100644 --- a/src/lib/Layout/TextTransition.svelte +++ b/src/lib/Layout/TextTransition.svelte @@ -1,24 +1,37 @@ <script> + import { run } from 'svelte/legacy'; + import { tweened } from 'svelte/motion'; import { cubicOut } from 'svelte/easing'; - export let text = ''; - export let opacityTransitionDuration = 50; - export let blurTransitionDuration = opacityTransitionDuration; - export let easing = cubicOut; + /** + * @typedef {Object} Props + * @property {string} [text] + * @property {number} [opacityTransitionDuration] + * @property {any} [blurTransitionDuration] + * @property {any} [easing] + */ + + /** @type {Props} */ + let { + text = '', + opacityTransitionDuration = 50, + blurTransitionDuration = opacityTransitionDuration, + easing = cubicOut + } = $props(); - let previousValue = ''; + let previousValue = $state(''); let opacity = tweened(1, { duration: opacityTransitionDuration, easing }); let blur = tweened(0, { duration: blurTransitionDuration, easing }); - $: { + run(() => { if (text !== previousValue) Promise.all([opacity.set(0), blur.set(10)]).then(() => { previousValue = text; Promise.all([opacity.set(1), blur.set(0)]); }); - } + }); </script> <span diff --git a/src/lib/Layout/Username.svelte b/src/lib/Layout/Username.svelte index 8b89b708..82be59d0 100644 --- a/src/lib/Layout/Username.svelte +++ b/src/lib/Layout/Username.svelte @@ -1,5 +1,9 @@ <script lang="ts"> - export let username: string; + interface Props { + username: string; + } + + let { username }: Props = $props(); </script> <a href={`https://anilist.co/user/${username}/`}>@{username}</a> diff --git a/src/lib/Lazy.svelte b/src/lib/Lazy.svelte index da5b09a9..8167711d 100644 --- a/src/lib/Lazy.svelte +++ b/src/lib/Lazy.svelte @@ -1,17 +1,31 @@ <script lang="ts"> import { onMount, onDestroy } from 'svelte'; - export let top = 0; - export let bottom = 0; - export let left = 0; - export let right = 0; - export let steps = 100; - export let threshold = 0.01; - export let once = false; - - let element: Element; - let percent: number = 0; - let visible = false; + interface Props { + top?: number; + bottom?: number; + left?: number; + right?: number; + steps?: number; + threshold?: number; + once?: boolean; + children?: import('svelte').Snippet<[any]>; + } + + let { + top = 0, + bottom = 0, + left = 0, + right = 0, + steps = 100, + threshold = 0.01, + once = false, + children + }: Props = $props(); + + let element: Element = $state(); + let percent: number = $state(0); + let visible = $state(false); let observer: IntersectionObserver; const intersectPercent = ( @@ -45,5 +59,5 @@ </script> <div bind:this={element}> - <slot {visible} {percent} /> + {@render children?.({ visible, percent, })} </div> diff --git a/src/lib/List/Anime/AnimeListTemplate.svelte b/src/lib/List/Anime/AnimeListTemplate.svelte index 08583d7c..8b105df5 100644 --- a/src/lib/List/Anime/AnimeListTemplate.svelte +++ b/src/lib/List/Anime/AnimeListTemplate.svelte @@ -14,26 +14,41 @@ import subsPlease from '$stores/subsPlease'; import identity from '$stores/identity'; - export let endTime: number; - export let cleanMedia: ( + interface Props { + endTime: number; + cleanMedia: ( media: Media[], displayUnresolved: boolean, subsPlease: SubsPlease | null, plannedOnly?: boolean ) => Media[]; - export let animeLists: Promise<Media[]>; - export let user: AniListAuthorisation; - export let title: any; - export let completed = false; - export let plannedOnly = false; - export let upcoming = false; - export let notYetReleased = false; - export let dummy = false; + animeLists: Promise<Media[]>; + user: AniListAuthorisation; + title: any; + completed?: boolean; + plannedOnly?: boolean; + upcoming?: boolean; + notYetReleased?: boolean; + dummy?: boolean; + } - let lastUpdatedMedia = -1; - let previousAnimeList: Media[]; - let pendingUpdate: number | null = null; - let lastListSize = 8; + let { + endTime, + cleanMedia, + animeLists = $bindable(), + user, + title, + completed = false, + plannedOnly = false, + upcoming = false, + notYetReleased = false, + dummy = false + }: Props = $props(); + + let lastUpdatedMedia = $state(-1); + let previousAnimeList: Media[] = $state(); + let pendingUpdate: number | null = $state(null); + let lastListSize = $state(8); onMount(() => { if (browser) { diff --git a/src/lib/List/Anime/CleanAnimeList.svelte b/src/lib/List/Anime/CleanAnimeList.svelte index 0220a366..b088964e 100644 --- a/src/lib/List/Anime/CleanAnimeList.svelte +++ b/src/lib/List/Anime/CleanAnimeList.svelte @@ -16,18 +16,35 @@ import CleanGrid from '$lib/List/CleanGrid.svelte'; import CleanList from '../CleanList.svelte'; - export let media: Media[]; - export let title: any; - export let animeLists: Promise<Media[]>; - export let user: AniListAuthorisation; - export let endTime: number; - export let lastUpdatedMedia: number; - export let completed = false; - export let previousAnimeList: Media[]; - export let pendingUpdate: number | null; - export let upcoming = false; - export let notYetReleased = false; - export let dummy = false; + interface Props { + media: Media[]; + title: any; + animeLists: Promise<Media[]>; + user: AniListAuthorisation; + endTime: number; + lastUpdatedMedia: number; + completed?: boolean; + previousAnimeList: Media[]; + pendingUpdate: number | null; + upcoming?: boolean; + notYetReleased?: boolean; + dummy?: boolean; + } + + let { + media = $bindable(), + title, + animeLists = $bindable(), + user, + endTime, + lastUpdatedMedia = $bindable(), + completed = false, + previousAnimeList = $bindable(), + pendingUpdate = $bindable(), + upcoming = false, + notYetReleased = false, + dummy = false + }: Props = $props(); let keyCacher: NodeJS.Timeout; let totalEpisodeDueCount = media @@ -109,60 +126,64 @@ /> {#if media.length === 0} - No anime to display. <button on:click={() => (animeLists = cleanCache(user, $identity))}> + No anime to display. <button onclick={() => (animeLists = cleanCache(user, $identity))}> Force refresh </button> {/if} {#if $settings.displayCoverModeAnime} <CleanGrid {media} {dummy} type="anime" {upcoming} {notYetReleased}> - <div slot="title" let:title={anime} let:progress> - {#if !upcoming && !notYetReleased} - {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} - <button - class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} - style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} - on:click={() => increment(anime, progress)}>+</button - > - {#if !completed || dummy} - [{anime.nextAiringEpisode?.episode === -1 - ? '?' - : (anime.nextAiringEpisode?.episode || 1) - 1}] - <br /> - <AiringTime originalAnime={anime} /> + {#snippet title({ title: anime, progress })} + <div > + {#if !upcoming && !notYetReleased} + {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} + <button + class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} + style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} + onclick={() => increment(anime, progress)}>+</button + > + {#if !completed || dummy} + [{anime.nextAiringEpisode?.episode === -1 + ? '?' + : (anime.nextAiringEpisode?.episode || 1) - 1}] + <br /> + <AiringTime originalAnime={anime} /> + {/if} + {:else} + <AiringTime originalAnime={anime} upcoming={true} /> {/if} - {:else} - <AiringTime originalAnime={anime} upcoming={true} /> - {/if} - </div> + </div> + {/snippet} </CleanGrid> {:else} <CleanList {media} type="anime" {upcoming} {notYetReleased} {lastUpdatedMedia}> - <span slot="information" let:title={anime} let:progress> - {#if !upcoming || notYetReleased || !$settings.displayCountdownRightAligned} - <span class="opaque">|</span> - {/if} - {#if !upcoming || notYetReleased} - <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} --> - {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} - <button - class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} - style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} - on:click={() => increment(anime, progress)}>+</button - > - {#if !completed} - [{anime.nextAiringEpisode?.episode === -1 - ? '?' - : (anime.nextAiringEpisode?.episode || 1) - 1}] + {#snippet information({ title: anime, progress })} + <span > + {#if !upcoming || notYetReleased || !$settings.displayCountdownRightAligned} + <span class="opaque">|</span> + {/if} + {#if !upcoming || notYetReleased} + <!-- {anime.mediaListEntry?.progress || 0}{@html totalEpisodes(anime)} --> + {pendingUpdate === anime.id ? progress + 1 : progress}{@html totalEpisodes(anime)} + <button + class={`button-square button-action ${pendingUpdate === anime.id ? 'opaque' : ''}`} + style={pendingUpdate === anime.id ? 'pointer-events: none;' : ''} + onclick={() => increment(anime, progress)}>+</button + > + {#if !completed} + [{anime.nextAiringEpisode?.episode === -1 + ? '?' + : (anime.nextAiringEpisode?.episode || 1) - 1}] + <span class:countdown={$settings.displayCountdownRightAligned}> + <AiringTime originalAnime={anime} /> + </span> + {/if} + {:else} <span class:countdown={$settings.displayCountdownRightAligned}> - <AiringTime originalAnime={anime} /> + <AiringTime originalAnime={anime} upcoming={true} /> </span> {/if} - {:else} - <span class:countdown={$settings.displayCountdownRightAligned}> - <AiringTime originalAnime={anime} upcoming={true} /> - </span> - {/if} - </span> + </span> + {/snippet} </CleanList> {/if} diff --git a/src/lib/List/Anime/CompletedAnimeList.svelte b/src/lib/List/Anime/CompletedAnimeList.svelte index 76ec7207..a8cb6fd1 100644 --- a/src/lib/List/Anime/CompletedAnimeList.svelte +++ b/src/lib/List/Anime/CompletedAnimeList.svelte @@ -11,18 +11,22 @@ import identity from '$stores/identity'; import sampleAnime from '$lib/Data/Static/SampleMedia/anime.json'; - export let user: AniListAuthorisation = { + interface Props { + user?: AniListAuthorisation; + dummy?: boolean; + } + + let { user = { accessToken: '', refreshToken: '', expiresIn: 0, tokenType: '' - }; - export let dummy = false; + }, dummy = false }: Props = $props(); const { addNotification } = getNotificationsContext(); - let animeLists: Promise<Media[]>; + let animeLists: Promise<Media[]> = $state(); let startTime: number; - let endTime: number; + let endTime: number = $state(); onMount(async () => { startTime = performance.now(); diff --git a/src/lib/List/Anime/DueAnimeList.svelte b/src/lib/List/Anime/DueAnimeList.svelte index 1453baeb..bbae319a 100644 --- a/src/lib/List/Anime/DueAnimeList.svelte +++ b/src/lib/List/Anime/DueAnimeList.svelte @@ -12,12 +12,16 @@ import locale from '$stores/locale'; import identity from '$stores/identity'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } + + let { user }: Props = $props(); const { addNotification } = getNotificationsContext(); - let animeLists: Promise<Media[]>; + let animeLists: Promise<Media[]> = $state(); let startTime: number; - let endTime: number; + let endTime: number = $state(); const keyCacher = setInterval(() => { startTime = performance.now(); diff --git a/src/lib/List/Anime/DueIndexColumn.svelte b/src/lib/List/Anime/DueIndexColumn.svelte index 61f2a178..0f20a2a1 100644 --- a/src/lib/List/Anime/DueIndexColumn.svelte +++ b/src/lib/List/Anime/DueIndexColumn.svelte @@ -6,8 +6,12 @@ import ListTitle from '../ListTitle.svelte'; import AnimeList from '$lib/List/Anime/DueAnimeList.svelte'; - export let userIdentity: { id: number }; - export let user: AniListAuthorisation; + interface Props { + userIdentity: { id: number }; + user: AniListAuthorisation; + } + + let { userIdentity, user }: Props = $props(); </script> <details open={!$settings.displayAnimeCollapsed} class="list list-due"> diff --git a/src/lib/List/Anime/PlaceholderList.svelte b/src/lib/List/Anime/PlaceholderList.svelte index 1f701d79..2c9baab5 100644 --- a/src/lib/List/Anime/PlaceholderList.svelte +++ b/src/lib/List/Anime/PlaceholderList.svelte @@ -4,8 +4,12 @@ import ListTitle from '../ListTitle.svelte'; import type { Title } from '../mediaTitle'; - export let title: Title; - export let count = 8; + interface Props { + title: Title; + count?: number; + } + + let { title, count = 8 }: Props = $props(); </script> <ListTitle {title} /> diff --git a/src/lib/List/Anime/UpcomingAnimeList.svelte b/src/lib/List/Anime/UpcomingAnimeList.svelte index 7b01af86..4957cd45 100644 --- a/src/lib/List/Anime/UpcomingAnimeList.svelte +++ b/src/lib/List/Anime/UpcomingAnimeList.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { mediaListCollection, Type, type Media } from '$lib/Data/AniList/media'; import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; import { onMount } from 'svelte'; @@ -13,12 +15,16 @@ import { injectAiringTime } from '$lib/Media/Anime/Airing/Subtitled/match'; import revalidateAnime from '$stores/revalidateAnime'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } + + let { user }: Props = $props(); const { addNotification } = getNotificationsContext(); - let animeLists: Promise<Media[]>; + let animeLists: Promise<Media[]> = $state(); let startTime: number; - let endTime: number; + let endTime: number = $state(); onMount(async () => { startTime = performance.now(); @@ -74,7 +80,7 @@ return upcomingAnime; }; - $: { + run(() => { if ($revalidateAnime) { $revalidateAnime = false; $lastPruneTimes.anime = -1; @@ -83,7 +89,7 @@ notificationType: 'Upcoming Episodes' }); } - } + }); </script> <AnimeList @@ -96,7 +102,7 @@ /> {#if $settings.displayPlannedAnime} - <p /> + <p></p> <AnimeList {endTime} diff --git a/src/lib/List/CleanGrid.svelte b/src/lib/List/CleanGrid.svelte index ec93a685..774303aa 100644 --- a/src/lib/List/CleanGrid.svelte +++ b/src/lib/List/CleanGrid.svelte @@ -7,11 +7,13 @@ import { mediaTitle } from './mediaTitle'; import './covers.css'; - export let media: Media[]; - export let dummy = false; - export let type: 'anime' | 'manga'; - export let upcoming = false; - export let notYetReleased = false; + let { + media = [] as Media[], + dummy = false, + type = 'anime' as 'anime' | 'manga', + upcoming = false, + notYetReleased = false + } = $props(); let uniqueID = new Date().getTime(); </script> diff --git a/src/lib/List/CleanList.svelte b/src/lib/List/CleanList.svelte index 47811932..46946973 100644 --- a/src/lib/List/CleanList.svelte +++ b/src/lib/List/CleanList.svelte @@ -5,11 +5,23 @@ import LinkedTooltip from '$lib/Tooltip/LinkedTooltip.svelte'; import settings from '$stores/settings'; - export let media: Media[]; - export let type: 'anime' | 'manga'; - export let upcoming = false; - export let notYetReleased = false; - export let lastUpdatedMedia: number; + interface Props { + media: Media[]; + type: 'anime' | 'manga'; + upcoming?: boolean; + notYetReleased?: boolean; + lastUpdatedMedia: number; + information?: import('svelte').Snippet<[any]>; + } + + let { + media, + type, + upcoming = false, + notYetReleased = false, + lastUpdatedMedia, + information + }: Props = $props(); </script> <ul> @@ -32,7 +44,7 @@ href={$settings.displayCopyMediaTitleNotLink ? '#' : outboundLink(title, type, $settings.displayOutboundLinksTo)} - on:click={(e) => { + onclick={(e) => { if ($settings.displayCopyMediaTitleNotLink) { e.preventDefault(); @@ -54,7 +66,7 @@ [<a href={`https://anilist.co/${type}/${title.id}/social`} target="_blank">S</a>] {/if} - <slot name="information" {progress} {title} /> + {@render information?.({ progress, title, })} </span> </li> {/if} diff --git a/src/lib/List/ListTitle.svelte b/src/lib/List/ListTitle.svelte index 21013b52..cccc5731 100644 --- a/src/lib/List/ListTitle.svelte +++ b/src/lib/List/ListTitle.svelte @@ -2,15 +2,28 @@ import tooltip from '$lib/Tooltip/tooltip'; import type { Title } from './mediaTitle'; - export let time: number | undefined = undefined; - export let count = -1337; - export let title: Title = { + interface Props { + time?: number | undefined; + count?: any; + title?: Title; + progress?: undefined | number; + hideTime?: boolean; + hideCount?: boolean; + children?: import('svelte').Snippet; + } + + let { + time = undefined, + count = -1337, + title = { title: 'Media List', hint: 'This is a media list.' - }; - export let progress: undefined | number = undefined; - export let hideTime = false; - export let hideCount = false; + }, + progress = undefined, + hideTime = false, + hideCount = false, + children + }: Props = $props(); </script> <summary> @@ -23,7 +36,7 @@ {#if !hideTime} <small class="opaque">{time ? time.toFixed(3) : '...'}s</small> {/if} - <slot /> + {@render children?.()} {#if progress !== undefined} <button class="badge unclickable-button button-badge badge-info"> {progress.toFixed(0)}% diff --git a/src/lib/List/Manga/CleanMangaList.svelte b/src/lib/List/Manga/CleanMangaList.svelte index dfaa187c..ec1a3d83 100644 --- a/src/lib/List/Manga/CleanMangaList.svelte +++ b/src/lib/List/Manga/CleanMangaList.svelte @@ -14,22 +14,37 @@ import CleanGrid from '../CleanGrid.svelte'; import CleanList from '../CleanList.svelte'; - export let media: Media[]; - export let cleanCache: () => void; - export let endTime: number; - export let lastUpdatedMedia: number; - export let updateMedia: ( + interface Props { + media: Media[]; + cleanCache: () => void; + endTime: number; + lastUpdatedMedia: number; + updateMedia: ( id: number, progress: number | undefined, media: Media[] ) => Promise<void>; - export let pendingUpdate: number | null; - export let due: boolean; - export let rateLimited: boolean; - export let authorised: boolean; - export let dummy = false; + pendingUpdate: number | null; + due: boolean; + rateLimited: boolean; + authorised: boolean; + dummy?: boolean; + } - let serviceStatusResponse: Promise<Response>; + let { + media, + cleanCache, + endTime, + lastUpdatedMedia, + updateMedia, + pendingUpdate, + due, + rateLimited, + authorised, + dummy = false + }: Props = $props(); + + let serviceStatusResponse: Promise<Response> = $state(); onMount(() => { serviceStatusResponse = fetch(proxy('https://api.mangadex.org/ping')); @@ -58,7 +73,7 @@ <button class="small-button" title="Force a full refresh" - on:click={cleanCache} + onclick={cleanCache} data-umami-event="Force Refresh Manga">Refresh</button > {/if} @@ -90,18 +105,18 @@ {#if media.length === 0 && !rateLimited} {#if rateLimited} - <p /> + <p></p> {/if} <p> - No manga to display. <button on:click={cleanCache} data-umami-event="Force Refresh No Manga" + No manga to display. <button onclick={cleanCache} data-umami-event="Force Refresh No Manga" >Force refresh</button > </p> <span> Don't read manga? <button - on:click={() => ($settings.disableManga = true)} + onclick={() => ($settings.disableManga = true)} data-umami-event="Disable No Manga">Hide the manga panel</button > You can re-enable it later in the <a href={root('/settings')}>Settings</a>. @@ -110,57 +125,61 @@ {#if $settings.displayCoverModeManga || dummy} <CleanGrid {media} {dummy} type="manga"> - <div slot="title" let:title={manga} let:progress> - {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} - <span class="opaque">/{manga.chapters || '?'}</span> - {/if} - <button - class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} - style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} - on:click={() => increment(manga)} - > - + - </button> - {#if due || Math.floor(manga.episodes) < manga.chapters} - [{manga.episodes || '?'}] - {#await volumeCount(manga) then volumes} - {@const volumeProgress = manga.mediaListEntry?.progressVolumes} + {#snippet title({ title: manga, progress })} + <div > + {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} + <span class="opaque">/{manga.chapters || '?'}</span> + {/if} + <button + class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} + style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} + onclick={() => increment(manga)} + > + + + </button> + {#if due || Math.floor(manga.episodes) < manga.chapters} + [{manga.episodes || '?'}] + {#await volumeCount(manga) then volumes} + {@const volumeProgress = manga.mediaListEntry?.progressVolumes} - {#if volumes !== null && (volumeProgress || 0) < volumes} - <span style="color: lightcoral;"> - Vol. {volumeProgress} → {volumes} - </span> - {/if} - {/await} - {/if} - </div> + {#if volumes !== null && (volumeProgress || 0) < volumes} + <span style="color: lightcoral;"> + Vol. {volumeProgress} → {volumes} + </span> + {/if} + {/await} + {/if} + </div> + {/snippet} </CleanGrid> {:else} <CleanList {media} type="manga" {lastUpdatedMedia}> - <span slot="information" let:title={manga} let:progress> - <span class="opaque">|</span> - {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} - <span class="opaque">/{manga.chapters || '?'}</span> - {/if} - <button - class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} - style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} - on:click={() => increment(manga)} - > - + - </button> - {#if due || Math.floor(manga.episodes) < manga.chapters} - [{manga.episodes || '?'}] - {#await volumeCount(manga) then volumes} - {@const volumeProgress = manga.mediaListEntry?.progressVolumes} + {#snippet information({ title: manga, progress })} + <span > + <span class="opaque">|</span> + {pendingUpdate === manga.id ? progress + 1 : progress}{#if !due} + <span class="opaque">/{manga.chapters || '?'}</span> + {/if} + <button + class={`button-square button-action ${pendingUpdate === manga.id ? 'opaque' : ''}`} + style={pendingUpdate === manga.id ? 'pointer-events: none;' : ''} + onclick={() => increment(manga)} + > + + + </button> + {#if due || Math.floor(manga.episodes) < manga.chapters} + [{manga.episodes || '?'}] + {#await volumeCount(manga) then volumes} + {@const volumeProgress = manga.mediaListEntry?.progressVolumes} - {#if volumes !== null && (volumeProgress || 0) < volumes} - <span style="color: lightcoral;"> - Vol. {volumeProgress} → {volumes} - </span> - {/if} - {/await} - {/if} - </span> + {#if volumes !== null && (volumeProgress || 0) < volumes} + <span style="color: lightcoral;"> + Vol. {volumeProgress} → {volumes} + </span> + {/if} + {/await} + {/if} + </span> + {/snippet} </CleanList> {/if} diff --git a/src/lib/List/Manga/MangaListTemplate.svelte b/src/lib/List/Manga/MangaListTemplate.svelte index 1303419f..107ff47d 100644 --- a/src/lib/List/Manga/MangaListTemplate.svelte +++ b/src/lib/List/Manga/MangaListTemplate.svelte @@ -21,28 +21,37 @@ import { browser } from '$app/environment'; import identity from '$stores/identity'; - export let user: AniListAuthorisation = { + interface Props { + user?: AniListAuthorisation; + displayUnresolved: boolean; + due: boolean; + dummy?: any; + } + + let { + user = { accessToken: '', refreshToken: '', expiresIn: 0, tokenType: '' - }; - export let displayUnresolved: boolean; - export let due: boolean; - export let dummy = $settings.debugDummyLists || false; + }, + displayUnresolved, + due, + dummy = $settings.debugDummyLists || false + }: Props = $props(); const { addNotification } = getNotificationsContext(); const authorised = authorisedJson.includes($identity.id); - let mangaLists: Promise<Media[]>; + let mangaLists: Promise<Media[]> = $state(); let startTime: number; - let endTime: number; - let lastUpdatedMedia = -1; - let previousMangaList: Media[]; - let pendingUpdate: number | null = null; - let progress = 0; - let rateLimited = false; - let forceFlag = false; - let lastListSize = 5; + let endTime: number = $state(); + let lastUpdatedMedia = $state(-1); + let previousMangaList: Media[] = $state(); + let pendingUpdate: number | null = $state(null); + let progress = $state(0); + let rateLimited = $state(false); + let forceFlag = $state(false); + let lastListSize = $state(5); const keyCacher = setInterval(() => { startTime = performance.now(); @@ -270,7 +279,7 @@ <button data-umami-event="Force Refresh Manga" title="Force a full refresh" - on:click={() => { + onclick={() => { cleanCache(); forceFlag = true; @@ -317,7 +326,7 @@ <button data-umami-event="Force Refresh Manga" title="Force a full refresh" - on:click={() => { + onclick={() => { cleanCache(); forceFlag = true; @@ -349,7 +358,7 @@ <button data-umami-event="Force Refresh Manga" title="Force a full refresh" - on:click={() => { + onclick={() => { cleanCache(); forceFlag = true; diff --git a/src/lib/List/MediaTitleDisplay.svelte b/src/lib/List/MediaTitleDisplay.svelte index 6a886704..0fd7cf17 100644 --- a/src/lib/List/MediaTitleDisplay.svelte +++ b/src/lib/List/MediaTitleDisplay.svelte @@ -6,10 +6,19 @@ import LZString from 'lz-string'; import * as wanakana from 'wanakana'; - export let title: MediaTitle; - export let abbreviate = false; - export let abbreviateTo = 20; - export let tooltip = false; + interface Props { + title: MediaTitle; + abbreviate?: boolean; + abbreviateTo?: number; + tooltip?: boolean; + } + + let { + title, + abbreviate = false, + abbreviateTo = 20, + tooltip = false + }: Props = $props(); const compressToBase64 = (string: string) => LZString.compressToBase64(string); </script> diff --git a/src/lib/Loading/Ellipsis.svelte b/src/lib/Loading/Ellipsis.svelte index ba1f30b8..7ee9dcfb 100644 --- a/src/lib/Loading/Ellipsis.svelte +++ b/src/lib/Loading/Ellipsis.svelte @@ -1,10 +1,14 @@ <script lang="ts"> - export let colour = 'var(--fg)'; + interface Props { + colour?: string; + } + + let { colour = 'var(--fg)' }: Props = $props(); </script> <div class="ellipsis" style={`--loader-colour: ${colour};`}> {#each Array.from({ length: 4 }) as _} - <div /> + <div></div> {/each} </div> diff --git a/src/lib/Loading/Grid.svelte b/src/lib/Loading/Grid.svelte index 1a64b3e0..107b985b 100644 --- a/src/lib/Loading/Grid.svelte +++ b/src/lib/Loading/Grid.svelte @@ -1,10 +1,14 @@ <script lang="ts"> - export let colour = 'var(--fg)'; + interface Props { + colour?: string; + } + + let { colour = 'var(--fg)' }: Props = $props(); </script> <div class="grid" style={`--loader-colour: ${colour};`}> {#each Array.from({ length: 9 }) as _} - <div /> + <div></div> {/each} </div> diff --git a/src/lib/Loading/Message.svelte b/src/lib/Loading/Message.svelte index 5aabec9c..c940a452 100644 --- a/src/lib/Loading/Message.svelte +++ b/src/lib/Loading/Message.svelte @@ -4,12 +4,25 @@ import Grid from './Grid.svelte'; import Popup from '$lib/Layout/Popup.svelte'; - export let message: string | undefined = undefined; - export let loader: 'ellipsis' | 'ripple' | 'grid' = 'ellipsis'; - export let colour = 'var(--fg)'; - export let slot = false; - export let withReload = false; - export let fullscreen = true; + interface Props { + message?: string | undefined; + loader?: 'ellipsis' | 'ripple' | 'grid'; + colour?: string; + slot?: boolean; + withReload?: boolean; + fullscreen?: boolean; + children?: import('svelte').Snippet; + } + + let { + message = undefined, + loader = 'ellipsis', + colour = 'var(--fg)', + slot = false, + withReload = false, + fullscreen = true, + children + }: Props = $props(); </script> <Popup {fullscreen} locked> @@ -29,10 +42,10 @@ {:else if slot} <br /> - <slot /> + {@render children?.()} {#if withReload} - Please <a href={'#'} on:click={() => location.reload()}>try again</a> later. + Please <a href={'#'} onclick={() => location.reload()}>try again</a> later. {/if} {/if} </div> diff --git a/src/lib/Loading/Ripple.svelte b/src/lib/Loading/Ripple.svelte index 05d62bb5..20b1447a 100644 --- a/src/lib/Loading/Ripple.svelte +++ b/src/lib/Loading/Ripple.svelte @@ -1,10 +1,14 @@ <script lang="ts"> - export let colour = 'var(--fg)'; + interface Props { + colour?: string; + } + + let { colour = 'var(--fg)' }: Props = $props(); </script> <div class="ripple" style={`--loader-colour: ${colour};`}> - <div /> - <div /> + <div></div> + <div></div> </div> <style lang="scss"> diff --git a/src/lib/Loading/Skeleton.svelte b/src/lib/Loading/Skeleton.svelte index 3f39beec..5ca3789a 100644 --- a/src/lib/Loading/Skeleton.svelte +++ b/src/lib/Loading/Skeleton.svelte @@ -1,12 +1,25 @@ <script lang="ts"> - export let count = 3; - export let width = '100%'; - export let height = '100px'; - export let card = true; - export let grid = false; - export let list = false; - export let pad = false; - export let bigCard = false; + interface Props { + count?: number; + width?: string; + height?: string; + card?: boolean; + grid?: boolean; + list?: boolean; + pad?: boolean; + bigCard?: boolean; + } + + let { + count = 3, + width = '100%', + height = '100px', + card = true, + grid = false, + list = false, + pad = false, + bigCard = false + }: Props = $props(); </script> {#if grid} @@ -14,7 +27,7 @@ {#each Array(count) as _, i} <div class={card ? `${bigCard ? 'card' : ''} card-small` : ''} style={`width: ${width};`}> <div class="skeleton-container" style={`--i: ${i};`}> - <div class="skeleton" style={`width: ${width}; height: ${height};`} /> + <div class="skeleton" style={`width: ${width}; height: ${height};`}></div> </div> </div> {/each} @@ -26,12 +39,12 @@ style={`width: ${width}; ${list ? 'padding-top: .75em;' : ''}; --i: ${i};`} > <div class="skeleton-container"> - <div class="skeleton" style={`width: ${width}; height: ${height};`} /> + <div class="skeleton" style={`width: ${width}; height: ${height};`}></div> </div> </div> {#if !list && i < count - 1} - <p /> + <p></p> {/if} {/each} {/if} diff --git a/src/lib/MarkdownLink.svelte b/src/lib/MarkdownLink.svelte index 731eb263..a4c62112 100644 --- a/src/lib/MarkdownLink.svelte +++ b/src/lib/MarkdownLink.svelte @@ -1,6 +1,10 @@ <script lang="ts"> - export let href: string; - export let text: string; + interface Props { + href: string; + text: string; + } + + let { href = $bindable(), text }: Props = $props(); try { let url = new URL(href); diff --git a/src/lib/Media/Anime/Airing/AiringTime.svelte b/src/lib/Media/Anime/Airing/AiringTime.svelte index 53a39c39..b9b224d7 100644 --- a/src/lib/Media/Anime/Airing/AiringTime.svelte +++ b/src/lib/Media/Anime/Airing/AiringTime.svelte @@ -6,16 +6,20 @@ import tooltip from '$lib/Tooltip/tooltip'; import locale from '$stores/locale'; - export let originalAnime: Media; - export let upcoming = false; + interface Props { + originalAnime: Media; + upcoming?: boolean; + } + + let { originalAnime, upcoming = false }: Props = $props(); const anime = originalAnime; - let opacity = 100; - let timeFrame = ''; - let time = ''; + let opacity = $state(100); + let timeFrame = $state(''); + let time = $state(''); let nextEpisode = anime.nextAiringEpisode?.episode || 0; - let few = true; - let dateString = ''; + let few = $state(true); + let dateString = $state(''); let updateInterval: NodeJS.Timeout; onMount(() => { diff --git a/src/lib/Media/Cover/HoverCover.svelte b/src/lib/Media/Cover/HoverCover.svelte index 51cbf5d2..f27be993 100644 --- a/src/lib/Media/Cover/HoverCover.svelte +++ b/src/lib/Media/Cover/HoverCover.svelte @@ -2,8 +2,12 @@ import settings from '$stores/settings'; import type { HoverCoverResponse } from './hoverCover'; - export let options: HoverCoverResponse; - export let width = 250; + interface Props { + options: HoverCoverResponse; + width?: number; + } + + let { options, width = 250 }: Props = $props(); </script> {#if options.hovering} diff --git a/src/lib/Notification/Notification.svelte b/src/lib/Notification/Notification.svelte index 6764f46e..f2b6ac44 100644 --- a/src/lib/Notification/Notification.svelte +++ b/src/lib/Notification/Notification.svelte @@ -1,12 +1,18 @@ <script lang="ts"> + import { preventDefault } from 'svelte/legacy'; + import settings from '$stores/settings'; import { onMount } from 'svelte'; - export let notification: { [key: string]: any } = {}; - export let onRemove: () => void = () => { + interface Props { + notification?: { [key: string]: any }; + onRemove?: () => void; + removed?: boolean; + } + + let { notification = {}, onRemove = () => { return; - }; - export let removed = false; + }, removed = $bindable(false) }: Props = $props(); onMount(() => setTimeout(remove, notification.duration)); @@ -21,20 +27,20 @@ <div id="notification-container" class={removed ? 'fade-out' : 'fade-in'} - on:click={remove} - on:keydown={() => { + onclick={remove} + onkeydown={() => { return; }} role="button" tabindex="0" > {#if notification.description} - <!-- svelte-ignore a11y-no-noninteractive-element-interactions --> + <!-- svelte-ignore a11y_no_noninteractive_element_interactions --> <details open id="notification" - on:click|preventDefault={(event) => event.preventDefault()} - on:keydown={() => { + onclick={preventDefault((event) => event.preventDefault())} + onkeydown={() => { return; }} > diff --git a/src/lib/Reader/Chapters/MangaDex.svelte b/src/lib/Reader/Chapters/MangaDex.svelte index 44c0d8f6..4be69d02 100644 --- a/src/lib/Reader/Chapters/MangaDex.svelte +++ b/src/lib/Reader/Chapters/MangaDex.svelte @@ -1,5 +1,9 @@ <script lang="ts"> - export let data: any; + interface Props { + data: any; + } + + let { data }: Props = $props(); </script> <ul> diff --git a/src/lib/Reader/Chapters/Rawkuma.svelte b/src/lib/Reader/Chapters/Rawkuma.svelte index 720c65d5..671a0572 100644 --- a/src/lib/Reader/Chapters/Rawkuma.svelte +++ b/src/lib/Reader/Chapters/Rawkuma.svelte @@ -2,7 +2,11 @@ import { getChaptersFromText } from '$lib/Data/Manga/raw'; import { onMount } from 'svelte'; - export let data: string; + interface Props { + data: string; + } + + let { data }: Props = $props(); onMount(() => { console.log(); diff --git a/src/lib/Schedule/CoverBypass.svelte b/src/lib/Schedule/CoverBypass.svelte index e94ddd40..72b84eb8 100644 --- a/src/lib/Schedule/CoverBypass.svelte +++ b/src/lib/Schedule/CoverBypass.svelte @@ -8,10 +8,19 @@ import { abbreviate } from '$lib/Utility/string'; import settings from '$stores/settings'; - export let media: Media | null; - export let entry: SubsPleaseEpisode; - export let cover = true; - export let showTooltip = true; + interface Props { + media: Media | null; + entry: SubsPleaseEpisode; + cover?: boolean; + showTooltip?: boolean; + } + + let { + media, + entry, + cover = true, + showTooltip = true + }: Props = $props(); const abbreviateTo = 40; diff --git a/src/lib/Schedule/Crunchyroll.svelte b/src/lib/Schedule/Crunchyroll.svelte index d22cff42..8fc0e95a 100644 --- a/src/lib/Schedule/Crunchyroll.svelte +++ b/src/lib/Schedule/Crunchyroll.svelte @@ -29,7 +29,7 @@ (media) => media.day === 'soon' ); - $: columnCount = Math.ceil(Object.keys(days).length / 2); + let columnCount = $derived(Math.ceil(Object.keys(days).length / 2)); const ordinalSuffix = (i: number) => { const j = i % 10; @@ -61,7 +61,7 @@ </details> </div> - <p /> + <p></p> {/each} <div class="card day"> @@ -76,7 +76,7 @@ </details> </div> - <p /> + <p></p> <div class="card day"> <details open class="details-unstyled"> diff --git a/src/lib/Schedule/Days.svelte b/src/lib/Schedule/Days.svelte index f7a49029..51558240 100644 --- a/src/lib/Schedule/Days.svelte +++ b/src/lib/Schedule/Days.svelte @@ -18,15 +18,24 @@ import Skeleton from '$lib/Loading/Skeleton.svelte'; import Error from '$lib/Error/RateLimited.svelte'; - export let subsPlease: SubsPlease; - export let scheduledMedia: Partial<Media[]>; - export let forceListMode = false; - export let user; + interface Props { + subsPlease: SubsPlease; + scheduledMedia: Partial<Media[]>; + forceListMode?: boolean; + user: any; + } + + let { + subsPlease, + scheduledMedia, + forceListMode = false, + user + }: Props = $props(); const urlParameters = browser ? new URLSearchParams(window.location.search) : null; let day: string | null = parseOrDefault(urlParameters, 'day', null); - let mediaListPromise: Promise<Media[]>; + let mediaListPromise: Promise<Media[]> = $state(); onMount(async () => { if (user === undefined || $identity.id === -2) mediaListPromise = Promise.resolve([]); diff --git a/src/lib/Settings/Categories/Attributions.svelte b/src/lib/Settings/Categories/Attributions.svelte index 250b08c3..28f69a74 100644 --- a/src/lib/Settings/Categories/Attributions.svelte +++ b/src/lib/Settings/Categories/Attributions.svelte @@ -34,7 +34,7 @@ </li> --> </ul> -<p /> +<p></p> <details open class="card-clear"> <summary>Outbound Link Disclaimer</summary> diff --git a/src/lib/Settings/Categories/Cache.svelte b/src/lib/Settings/Categories/Cache.svelte index e65b8081..df0165d5 100644 --- a/src/lib/Settings/Categories/Cache.svelte +++ b/src/lib/Settings/Categories/Cache.svelte @@ -11,7 +11,7 @@ <a href="https://due.moe">due.moe</a>'s site data will clear these caches too. </small> -<p /> +<p></p> Re-cache AniList media lists every <input @@ -22,7 +22,7 @@ Re-cache AniList media lists every max="60" placeholder="30" size="1" - on:change={() => + onchange={() => ($settings.cacheMinutes < 1 && ($settings.cacheMinutes = 1)) || ($settings.cacheMinutes > 60 && ($settings.cacheMinutes = 60))} /> @@ -39,7 +39,7 @@ Re-cache manga data every max="1440" placeholder="120" size="2" - on:change={() => + onchange={() => ($settings.cacheMangaMinutes < 5 && ($settings.cacheMangaMinutes = 5)) || ($settings.cacheMangaMinutes > 1440 && ($settings.cacheMangaMinutes = 1440))} /> diff --git a/src/lib/Settings/Categories/Calculation.svelte b/src/lib/Settings/Categories/Calculation.svelte index 4a202fff..1f6eeaa1 100644 --- a/src/lib/Settings/Categories/Calculation.svelte +++ b/src/lib/Settings/Categories/Calculation.svelte @@ -75,7 +75,7 @@ {#if !$settings.calculateGuessingDisabled} <br /> - <select bind:value={$settings.calculateGuessMethod} on:change={pruneAllManga}> + <select bind:value={$settings.calculateGuessMethod} onchange={pruneAllManga}> <option value="mode">Mode (fast, moderate to low accuracy)</option> <option value="median">Median (moderate speed, high accuracy, recommended)</option> <option value="iqr_median">Interquartile Range with Median (slower, high accuracy)</option> diff --git a/src/lib/Settings/Categories/Debug.svelte b/src/lib/Settings/Categories/Debug.svelte index 79e18c03..00c5852e 100644 --- a/src/lib/Settings/Categories/Debug.svelte +++ b/src/lib/Settings/Categories/Debug.svelte @@ -12,7 +12,7 @@ <SettingCheckboxToggle setting="debugDummyLists" text={$locale().debug.dummyLists} /> <button - on:click={() => { + onclick={() => { localStorage.removeItem('anime'); localStorage.removeItem('manga'); addNotification( @@ -23,10 +23,10 @@ }}>{$locale().debug.clearCaches}</button > -<p /> +<p></p> <button - on:click={() => { + onclick={() => { settings.reset(); addNotification( options({ @@ -41,10 +41,10 @@ {$locale().debug.resetAllSettings.hint} </SettingHint> -<p /> +<p></p> <button - on:click={() => { + onclick={() => { localStorage.clear(); addNotification( options({ diff --git a/src/lib/Settings/Categories/Display.svelte b/src/lib/Settings/Categories/Display.svelte index 3b572b7b..a83157fb 100644 --- a/src/lib/Settings/Categories/Display.svelte +++ b/src/lib/Settings/Categories/Display.svelte @@ -85,7 +85,7 @@ <SettingHint lineBreak> Media where either the next episode's release date is unknown or the chapter count could not be resolved is considered unresolved. - <p /> + <p></p> <span> Additionally, you hard exclude specific media from <a href={root('/')}>due.moe</a> on AniList. To exclude any media from being included in <b>any</b> <a href={root('/')}>due.moe</a> @@ -107,7 +107,7 @@ </span> </SettingHint> -<p /> +<p></p> <b>{$locale().settings.display.categories.hidePanels}</b><br /> <SettingCheckboxToggle @@ -160,7 +160,7 @@ }} /> -<p /> +<p></p> <b>{$locale().settings.display.categories.collapsePanelsByDefault}</b><br /> <SettingCheckboxToggle @@ -175,7 +175,7 @@ /> <SettingCheckboxToggle setting="displayMangaCollapsed" text={$locale().settings.media.manga} /> -<p /> +<p></p> <b>{$locale().settings.display.categories.motionAndAccessibility.title}</b><br /> <SettingCheckboxToggle @@ -215,7 +215,7 @@ </button> {/if} -<p /> +<p></p> <SettingCheckboxToggle setting="displayAniListNotifications" @@ -242,7 +242,7 @@ </SettingHint> </SettingCheckboxToggle> -<p /> +<p></p> <b>{$locale().settings.display.categories.dateAndTime.title}</b><br /> <SettingCheckboxToggle @@ -262,7 +262,7 @@ text={$locale().settings.display.categories.dateAndTime.fields.abbreviateCountdown} /> -<p /> +<p></p> <SettingCheckboxToggle setting="displayDisableLastActivityWarning" @@ -277,7 +277,7 @@ </SettingHint> </SettingCheckboxToggle> -<p /> +<p></p> <b>Show lists with media covers instead of text</b><br /> <SettingCheckboxToggle setting="displayCoverModeAnime" text="Anime" lineBreak={false} /> @@ -297,7 +297,7 @@ min="50" placeholder="116.609" size="3" - on:change={() => { + onchange={() => { if ($settings.displayCoverWidth === null) { $settings.displayCoverWidth = 116.609; @@ -313,7 +313,7 @@ <br /> {/if} -<p /> +<p></p> <b>{$locale().settings.display.categories.media.title}</b><br /> <SettingCheckboxToggle @@ -357,7 +357,7 @@ <br /> {/if} -<p /> +<p></p> <select bind:value={$settings.displayOutboundLinksTo}> <option value="anilist">AniList</option> @@ -390,7 +390,7 @@ <br /> -<select bind:value={$settings.displayAoButa} on:change={onHelperChange}> +<select bind:value={$settings.displayAoButa} onchange={onHelperChange}> <option value="mai">{$locale().settings.display.categories.helper.options.mai}</option> <option value="mai_2">{$locale().settings.display.categories.helper.options.mai} #2</option> <option value="nodoka">{$locale().settings.display.categories.helper.options.nodoka}</option> diff --git a/src/lib/Settings/Categories/RSSFeeds.svelte b/src/lib/Settings/Categories/RSSFeeds.svelte index a225b137..9ced917b 100644 --- a/src/lib/Settings/Categories/RSSFeeds.svelte +++ b/src/lib/Settings/Categories/RSSFeeds.svelte @@ -6,13 +6,17 @@ import SettingHint from '../SettingHint.svelte'; import tooltip from '$lib/Tooltip/tooltip'; - export let user: any; + interface Props { + user: any; + } + + let { user }: Props = $props(); const { addNotification } = getNotificationsContext(); </script> <button - on:click={() => { + onclick={() => { addNotification( options({ heading: 'RSS feed URL copied to clipboard' @@ -31,7 +35,7 @@ Your AniList notifications RSS feed URL <SettingHint lineBreak> This <a href={'#'} - on:click={(e) => e.preventDefault()} + onclick={(e) => e.preventDefault()} target="_blank" title={$locale().settings.rssFeeds.tooltips.rss} use:tooltip diff --git a/src/lib/Settings/Categories/SettingSync.svelte b/src/lib/Settings/Categories/SettingSync.svelte index 39e62954..24a35c79 100644 --- a/src/lib/Settings/Categories/SettingSync.svelte +++ b/src/lib/Settings/Categories/SettingSync.svelte @@ -13,7 +13,7 @@ {#if !$settings.settingsSync} <button - on:click={() => { + onclick={() => { $settings.settingsSync = true; fetch(root(`/api/configuration?id=${$identity.id}`)).then((response) => { @@ -50,9 +50,9 @@ <SettingHint lineBreak> {$locale().settings.settingsSync.buttons.pull.hint} </SettingHint> - <p /> + <p></p> <button - on:click={() => { + onclick={() => { $settings.settingsSync = true; fetch(root(`/api/configuration`), { @@ -76,7 +76,7 @@ </SettingHint> {:else} <button - on:click={() => { + onclick={() => { $settings.settingsSync = false; addNotification( @@ -89,7 +89,7 @@ {$locale().settings.settingsSync.buttons.disable} </button> <button - on:click={() => { + onclick={() => { fetch(root(`/api/configuration?id=${$identity.id}`), { method: 'DELETE' }).then((response) => { @@ -108,7 +108,7 @@ {$locale().settings.settingsSync.buttons.delete} </button> - <p /> + <p></p> <b>Last Push</b>: {$locale().dateFormatter($settingsSyncTimes.lastPush)} <br /> diff --git a/src/lib/Settings/Category.svelte b/src/lib/Settings/Category.svelte index a5b3e211..aef86d77 100644 --- a/src/lib/Settings/Category.svelte +++ b/src/lib/Settings/Category.svelte @@ -1,8 +1,19 @@ <script lang="ts"> - export let title = ''; - export let id = title.toLowerCase(); - export let open = true; - export let newLine = true; + interface Props { + title?: string; + id?: any; + open?: boolean; + newLine?: boolean; + children?: import('svelte').Snippet; + } + + let { + title = '', + id = title.toLowerCase(), + open = true, + newLine = true, + children + }: Props = $props(); </script> <details {open} {id}> @@ -10,9 +21,9 @@ <summary>{title}</summary> {/if} - <slot /> + {@render children?.()} </details> {#if newLine} - <p /> + <p></p> {/if} diff --git a/src/lib/Settings/SettingCheckboxToggle.svelte b/src/lib/Settings/SettingCheckboxToggle.svelte index 6a16edec..85dbfcdc 100644 --- a/src/lib/Settings/SettingCheckboxToggle.svelte +++ b/src/lib/Settings/SettingCheckboxToggle.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import settings, { type Settings } from '$stores/settings'; type BooleanSettingsKeys<T> = { @@ -6,19 +8,37 @@ }; type SettingsBooleanKeys = BooleanSettingsKeys<Settings>; - export let sectionBreak = false; - export let disabled = false; - export let text: string | (() => string); - export let setting: SettingsBooleanKeys[keyof SettingsBooleanKeys]; - export let lineBreak = true; - export let onChange: () => void = () => { - return; - }; - export let invert = false; - export let id: string | null = null; - - $: checked = setting ? (invert ? !$settings[setting] : $settings[setting]) : false; - $: field = text instanceof Function ? text() : text; + interface Props { + sectionBreak?: boolean; + disabled?: boolean; + text: string | (() => string); + setting: SettingsBooleanKeys[keyof SettingsBooleanKeys]; + lineBreak?: boolean; + onChange?: () => void; + invert?: boolean; + id?: string | null; + children?: import('svelte').Snippet; + } + + let { + sectionBreak = false, + disabled = false, + text, + setting, + lineBreak = true, + onChange = () => { + return; + }, + invert = false, + id = null, + children + }: Props = $props(); + + let checked = $state(false); + run(() => { + checked = setting ? (invert ? !$settings[setting] : $settings[setting]) : false; + }); + let field = $derived(text instanceof Function ? text() : text); // const toggler = (key: keyof Settings) => [ // () => @@ -53,11 +73,11 @@ }; </script> -<input type="checkbox" on:change={check} bind:checked {id} /> +<input type="checkbox" onchange={check} bind:checked {id} /> <span - on:click={flip} - on:keydown={() => { + onclick={flip} + onkeydown={() => { return; }} role="button" @@ -72,12 +92,12 @@ {/if} </span> -<slot /> +{@render children?.()} {#if lineBreak} <br /> {/if} {#if sectionBreak} - <p /> + <p></p> {/if} diff --git a/src/lib/Settings/SettingHint.svelte b/src/lib/Settings/SettingHint.svelte index f82f061c..c5a9072f 100644 --- a/src/lib/Settings/SettingHint.svelte +++ b/src/lib/Settings/SettingHint.svelte @@ -1,5 +1,10 @@ <script lang="ts"> - export let lineBreak = false; + interface Props { + lineBreak?: boolean; + children?: import('svelte').Snippet; + } + + let { lineBreak = false, children }: Props = $props(); </script> {#if lineBreak} @@ -7,5 +12,5 @@ {/if} <small style="opacity: 80%;"> - <slot /> + {@render children?.()} </small> diff --git a/src/lib/Settings/SettingToggle.svelte b/src/lib/Settings/SettingToggle.svelte index 0d177b50..0d3b893a 100644 --- a/src/lib/Settings/SettingToggle.svelte +++ b/src/lib/Settings/SettingToggle.svelte @@ -1,16 +1,28 @@ <script lang="ts"> import settings, { type Settings } from '$stores/settings'; - export let setting: keyof Settings; - export let on = ''; - export let off = ''; - export let sectionBreak = false; - export let disabled = false; + interface Props { + setting: keyof Settings; + on?: string; + off?: string; + sectionBreak?: boolean; + disabled?: boolean; + children?: import('svelte').Snippet; + } + + let { + setting, + on = '', + off = '', + sectionBreak = false, + disabled = false, + children + }: Props = $props(); </script> <a href={'#'} - on:click={() => + onclick={() => disabled ? {} : $settings[setting] @@ -20,16 +32,16 @@ {#if disabled} <strike> {$settings[setting] ? on : off} - <slot /> + {@render children?.()} </strike> {:else} {$settings[setting] ? on : off} - <slot /> + {@render children?.()} {/if} </a> <br /> {#if sectionBreak} - <p /> + <p></p> {/if} diff --git a/src/lib/Tools/ActivityHistory/Grid.svelte b/src/lib/Tools/ActivityHistory/Grid.svelte index db9f3839..8789a786 100644 --- a/src/lib/Tools/ActivityHistory/Grid.svelte +++ b/src/lib/Tools/ActivityHistory/Grid.svelte @@ -12,12 +12,16 @@ import tooltip from '$lib/Tooltip/tooltip'; import LogInRestricted from '$lib/Error/LogInRestricted.svelte'; - export let user: AniListAuthorisation; - export let activityData: ActivityHistoryEntry[] | null = null; - export let currentYear = new Date().getFullYear(); + interface Props { + user: AniListAuthorisation; + activityData?: ActivityHistoryEntry[] | null; + currentYear?: any; + } + + let { user, activityData = null, currentYear = new Date().getFullYear() }: Props = $props(); - let activityHistoryData: ActivityHistoryEntry[]; - let baseHue = Math.floor(Math.random() * 360); + let activityHistoryData: ActivityHistoryEntry[] = $state(); + let baseHue = $state(Math.floor(Math.random() * 360)); onMount(async () => { clearAllParameters(); @@ -45,8 +49,8 @@ <div class="grid-item" style="background-color: {gradientColour(activity.amount, highestActivity, baseHue)}" - on:click={() => (baseHue = Math.floor(Math.random() * 360))} - on:keydown={() => { + onclick={() => (baseHue = Math.floor(Math.random() * 360))} + onkeydown={() => { return; }} role="button" @@ -55,7 +59,7 @@ title={`Date: ${new Date(activity.date * 1000).toLocaleDateString()}\nAmount: ${ activity.amount }`} - /> +></div> {/each} </div> {/if} diff --git a/src/lib/Tools/ActivityHistory/Tool.svelte b/src/lib/Tools/ActivityHistory/Tool.svelte index b6e66a5e..07b2096a 100644 --- a/src/lib/Tools/ActivityHistory/Tool.svelte +++ b/src/lib/Tools/ActivityHistory/Tool.svelte @@ -14,10 +14,14 @@ import Skeleton from '$lib/Loading/Skeleton.svelte'; import LogInRestricted from '$lib/Error/LogInRestricted.svelte'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } - let activityHistoryData: Promise<ActivityHistoryEntry[]>; - let generated = false; + let { user }: Props = $props(); + + let activityHistoryData: Promise<ActivityHistoryEntry[]> = $state(); + let generated = $state(false); onMount(async () => { clearAllParameters(); @@ -79,18 +83,18 @@ <div class="card"> <ActivityHistoryGrid {user} /> - <p /> + <p></p> - <div id="grid-final" /> + <div id="grid-final"></div> {#if generated} - <p /> + <p></p> {/if} - <button on:click={screenshot}>Generate grid image</button> + <button onclick={screenshot}>Generate grid image</button> </div> - <p /> + <p></p> <details open> <summary>Days in risk of developing an activity history hole</summary> diff --git a/src/lib/Tools/Birthdays.svelte b/src/lib/Tools/Birthdays.svelte index 97ff40d8..17c91709 100644 --- a/src/lib/Tools/Birthdays.svelte +++ b/src/lib/Tools/Birthdays.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { browser } from '$app/environment'; import { page } from '$app/stores'; import { ACDBBirthdays, type ACDBBirthday } from '$lib/Data/Birthday/secondary'; @@ -18,12 +20,12 @@ 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[]>; + let month = $state(parseOrDefault(urlParameters, 'month', date.getMonth() + 1)); + let day = $state(parseOrDefault(urlParameters, 'day', date.getDate())); + let anisearchBirthdays: Promise<aniSearchBirthday[]> = $state(); + let acdbBirthdays: Promise<ACDBBirthday[]> = $state(); - $: { + run(() => { month = Math.min(month, 12); month = Math.max(month, 1); day = Math.min(day, new Date(new Date().getFullYear(), month, 0).getDate()); @@ -39,7 +41,7 @@ clearAllParameters(['month', 'day']); history.replaceState(null, '', `?${$page.url.searchParams.toString()}`); } - } + }); onMount(() => clearAllParameters(['month', 'day'])); diff --git a/src/lib/Tools/DumpProfile.svelte b/src/lib/Tools/DumpProfile.svelte index 45d4ffc9..ac0184ae 100644 --- a/src/lib/Tools/DumpProfile.svelte +++ b/src/lib/Tools/DumpProfile.svelte @@ -5,7 +5,7 @@ import InputTemplate from './InputTemplate.svelte'; import LZString from 'lz-string'; - let submission = ''; + let submission = $state(''); // Credit: @hoh const decodeJSON = (about: string): JSON | null => { @@ -26,7 +26,7 @@ }; </script> -<!-- svelte-ignore missing-declaration --> +<!-- svelte-ignore missing_declaration --> <InputTemplate field="Username" bind:submission event="Dump User" submitText="Dump"> {#await dumpUser(submission)} <Skeleton card={false} count={1} height="500px" /> @@ -36,7 +36,7 @@ <pre>{JSON.stringify(dump, null, 2)}</pre> {#if decoded && (dump.about || '').includes('[](json')} - <p /> + <p></p> <pre>{JSON.stringify(decoded, null, 2).replaceAll(/\\n/g, '\n')}</pre> {/if} diff --git a/src/lib/Tools/EpisodeDiscussionCollector.svelte b/src/lib/Tools/EpisodeDiscussionCollector.svelte index 4c61f3cf..746dff22 100644 --- a/src/lib/Tools/EpisodeDiscussionCollector.svelte +++ b/src/lib/Tools/EpisodeDiscussionCollector.svelte @@ -6,7 +6,7 @@ import InputTemplate from './InputTemplate.svelte'; import tooltip from '$lib/Tooltip/tooltip'; - let submission = ''; + let submission = $state(''); onMount(clearAllParameters); </script> @@ -54,7 +54,7 @@ </p> {/await} {:else} - <p /> + <p></p> Enter a username to search for to continue. {/if} diff --git a/src/lib/Tools/FollowFix.svelte b/src/lib/Tools/FollowFix.svelte index b9fddeff..411b1a92 100644 --- a/src/lib/Tools/FollowFix.svelte +++ b/src/lib/Tools/FollowFix.svelte @@ -3,23 +3,27 @@ import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; import LogInRestricted from '$lib/Error/LogInRestricted.svelte'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } - let input = ''; - let submit = ''; + let { user }: Props = $props(); + + let input = $state(''); + let submit = $state(''); </script> {#if user === undefined} <LogInRestricted /> {:else} <p> - <!-- svelte-ignore missing-declaration --> + <!-- svelte-ignore missing_declaration --> <input type="text" minlength="1" placeholder="Username" bind:value={input} - on:keypress={(e) => { + onkeypress={(e) => { if (e.key === 'Enter') { submit = input; @@ -28,7 +32,7 @@ } }} /> - <a href={'#'} on:click={() => (submit = input)}> + <a href={'#'} onclick={() => (submit = input)}> Toggle follow for {input.length === 0 ? '...' : input} </a> </p> diff --git a/src/lib/Tools/Hayai.svelte b/src/lib/Tools/Hayai.svelte index 1790af53..127c07b9 100644 --- a/src/lib/Tools/Hayai.svelte +++ b/src/lib/Tools/Hayai.svelte @@ -90,13 +90,13 @@ )} </small> - <p /> + <p></p> {@html applyBionicReadingToString( `After selecting an EPUB file, 早い will apply a bionic reading filter over any and all words, and return the newly created "bionic" EPUB file.` )} - <p /> + <p></p> - <input type="file" id="epub-file" accept=".epub" on:change={handleFileUpload} /> + <input type="file" id="epub-file" accept=".epub" onchange={handleFileUpload} /> </div> diff --git a/src/lib/Tools/HololiveBirthdays.svelte b/src/lib/Tools/HololiveBirthdays.svelte index 68a591de..769f5d6f 100644 --- a/src/lib/Tools/HololiveBirthdays.svelte +++ b/src/lib/Tools/HololiveBirthdays.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { browser } from '$app/environment'; import { page } from '$app/stores'; import { onMount } from 'svelte'; @@ -9,14 +11,14 @@ 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 month = $state(parseOrDefault(urlParameters, 'month', date.getMonth() + 1)); + let day = $state(parseOrDefault(urlParameters, 'day', date.getDate())); - $: todaysBirthdays = birthdays.filter( + let todaysBirthdays = $derived(birthdays.filter( (birthday) => birthday.month === month && birthday.day === day - ); + )); - $: { + run(() => { month = Math.min(month, 12); month = Math.max(month, 1); day = Math.min(day, new Date(new Date().getFullYear(), month, 0).getDate()); @@ -28,7 +30,7 @@ clearAllParameters(['month', 'day']); history.replaceState(null, '', `?${$page.url.searchParams.toString()}`); } - } + }); onMount(() => clearAllParameters(['month', 'day'])); </script> diff --git a/src/lib/Tools/InputTemplate.svelte b/src/lib/Tools/InputTemplate.svelte index 72e2f807..52b9fd46 100644 --- a/src/lib/Tools/InputTemplate.svelte +++ b/src/lib/Tools/InputTemplate.svelte @@ -3,33 +3,49 @@ import { onMount } from 'svelte'; import SettingHint from '$lib/Settings/SettingHint.svelte'; - export let field: string; - export let submission: string; - export let event: string | undefined = undefined; - export let submitText: string; - export let saveParameters: string[] = []; - export let onSubmit = () => { + interface Props { + field: string; + submission: string; + event?: string | undefined; + submitText: string; + saveParameters?: string[]; + onSubmit?: any; + preserveCase?: boolean; + prompt?: any; + hint?: string | undefined; + children?: import('svelte').Snippet; + } + + let { + field, + submission = $bindable(), + event = undefined, + submitText, + saveParameters = [], + onSubmit = () => { return; - }; - export let preserveCase = false; - export let prompt = `Enter a ${ + }, + preserveCase = false, + prompt = `Enter a ${ preserveCase ? field : field.toLowerCase() - } to search for to continue.`; - export let hint: string | undefined = undefined; + } to search for to continue.`, + hint = undefined, + children + }: Props = $props(); - let input = ''; + let input = $state(''); onMount(() => clearAllParameters(saveParameters)); </script> <div class="card"> <p> - <!-- svelte-ignore missing-declaration --> + <!-- svelte-ignore missing_declaration --> <input type="text" placeholder={field} bind:value={input} - on:keypress={(e) => { + onkeypress={(e) => { if (e.key === 'Enter') { submission = input; @@ -42,7 +58,7 @@ /> <button class="button-lined" - on:click={() => { + onclick={() => { submission = input; onSubmit(); @@ -62,9 +78,9 @@ </p> {#if submission !== ''} - <slot /> + {@render children?.()} {:else} - <p /> + <p></p> {prompt} {/if} diff --git a/src/lib/Tools/Likes.svelte b/src/lib/Tools/Likes.svelte index 7b626c94..d86c5e12 100644 --- a/src/lib/Tools/Likes.svelte +++ b/src/lib/Tools/Likes.svelte @@ -7,16 +7,16 @@ import settings from '$stores/settings'; import InputTemplate from './InputTemplate.svelte'; - let submission = ''; + let submission = $state(''); - $: normalisedSubmission = submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$2'); - $: submissionType = submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$1'); - $: likesPromise = - submissionType === 'activity' + let normalisedSubmission = $derived(submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$2')); + let submissionType = $derived(submission.replace(/.*\/(activity|thread)\/(\d+).*/, '$1')); + let likesPromise = + $derived(submissionType === 'activity' ? activityLikes(Number(normalisedSubmission)) : submissionType === 'thread' ? threadLikes(Number(normalisedSubmission)) - : Promise.resolve(null); + : Promise.resolve(null)); </script> <InputTemplate diff --git a/src/lib/Tools/Picker.svelte b/src/lib/Tools/Picker.svelte index 3f20300f..8628f757 100644 --- a/src/lib/Tools/Picker.svelte +++ b/src/lib/Tools/Picker.svelte @@ -4,13 +4,17 @@ import root from '$lib/Utility/root'; import { tools } from './tools'; - export let tool: string; + interface Props { + tool: string; + } + + let { tool = $bindable() }: Props = $props(); </script> <blockquote> <select bind:value={tool} - on:change={() => { + onchange={() => { if (browser) goto(root(`/tools/${tool}`)); }} > diff --git a/src/lib/Tools/RandomFollower.svelte b/src/lib/Tools/RandomFollower.svelte index acb5a33a..30b58470 100644 --- a/src/lib/Tools/RandomFollower.svelte +++ b/src/lib/Tools/RandomFollower.svelte @@ -5,8 +5,8 @@ import TextSwap from '$lib/Layout/TextTransition.svelte'; import InputTemplate from './InputTemplate.svelte'; - let submission = ''; - let randomSeed = 0; + let submission = $state(''); + let randomSeed = $state(0); </script> <InputTemplate @@ -21,7 +21,7 @@ {:then users} {@const user = users[Math.floor(randomSeed * users.length)]} - <p /> + <p></p> <a href={`https://anilist.co/user/${user.id}`} target="_blank"> <TextSwap text={user.name} /> diff --git a/src/lib/Tools/SequelCatcher/List.svelte b/src/lib/Tools/SequelCatcher/List.svelte index 009df219..788e8e01 100644 --- a/src/lib/Tools/SequelCatcher/List.svelte +++ b/src/lib/Tools/SequelCatcher/List.svelte @@ -4,10 +4,14 @@ import { outboundLink } from '$lib/Media/links'; import settings from '$stores/settings'; - export let mediaListUnchecked: Media[]; + interface Props { + mediaListUnchecked: Media[]; + } + + let { mediaListUnchecked }: Props = $props(); - let includeCurrent = false; - let includeSideStories = false; + let includeCurrent = $state(false); + let includeSideStories = $state(false); const matchCheck = (media: Media | undefined, swap = false) => (media && @@ -30,7 +34,7 @@ paused) <input type="checkbox" bind:checked={includeSideStories} /> Include side stories (e.g., OVAs, specials, etc.) -<p /> +<p></p> <ol class="media-list"> {#each filterRelations( mediaListUnchecked.filter((media) => media.mediaListEntry?.status === 'COMPLETED'), includeSideStories ) as { media, unwatchedRelations }} diff --git a/src/lib/Tools/SequelCatcher/Tool.svelte b/src/lib/Tools/SequelCatcher/Tool.svelte index a954b4d7..547903b7 100644 --- a/src/lib/Tools/SequelCatcher/Tool.svelte +++ b/src/lib/Tools/SequelCatcher/Tool.svelte @@ -12,9 +12,13 @@ import Skeleton from '$lib/Loading/Skeleton.svelte'; import Username from '$lib/Layout/Username.svelte'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } - let mediaList: Promise<Media[]>; + let { user }: Props = $props(); + + let mediaList: Promise<Media[]> = $state(); onMount(async () => { if (user === undefined || $identity.id === -2) return; @@ -71,7 +75,7 @@ <Message message="" loader="ripple" slot withReload fullscreen>Error fetching media.</Message> {/await} - <p /> + <p></p> <blockquote style="margin: 0 0 0 1.5rem;"> Thanks to <Username username="sevengirl" /> and <Username username="esthereae" /> for the idea! diff --git a/src/lib/Tools/SequelSpy/Prequels.svelte b/src/lib/Tools/SequelSpy/Prequels.svelte index b22db3af..0b01b646 100644 --- a/src/lib/Tools/SequelSpy/Prequels.svelte +++ b/src/lib/Tools/SequelSpy/Prequels.svelte @@ -6,7 +6,11 @@ import settings from '$stores/settings'; import type { Media } from '$lib/Data/AniList/media'; - export let currentPrequels: MediaPrequel[]; + interface Props { + currentPrequels: MediaPrequel[]; + } + + let { currentPrequels }: Props = $props(); const prequelAiringTime = (prequel: MediaPrequel) => airingTime(prequel as unknown as Media, null, false, true); diff --git a/src/lib/Tools/SequelSpy/Tool.svelte b/src/lib/Tools/SequelSpy/Tool.svelte index caec4a46..b50e2f84 100644 --- a/src/lib/Tools/SequelSpy/Tool.svelte +++ b/src/lib/Tools/SequelSpy/Tool.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; import { prequels, type MediaPrequel } from '$lib/Data/AniList/prequels'; import { onMount } from 'svelte'; @@ -11,25 +13,29 @@ import LogInRestricted from '$lib/Error/LogInRestricted.svelte'; import Prequels from './Prequels.svelte'; - export let user: AniListAuthorisation; + interface Props { + user: AniListAuthorisation; + } + + let { user }: Props = $props(); - let currentPrequels: Promise<MediaPrequel[]> = Promise.resolve([]) as Promise<MediaPrequel[]>; + let currentPrequels: Promise<MediaPrequel[]> = $state(Promise.resolve([]) as Promise<MediaPrequel[]>); const urlParameters = browser ? new URLSearchParams(window.location.search) : null; - let year = parseOrDefault(urlParameters, 'year', new Date().getFullYear()); - let season = parseOrDefault(urlParameters, 'season', getSeason()); + let year = $state(parseOrDefault(urlParameters, 'year', new Date().getFullYear())); + let season = $state(parseOrDefault(urlParameters, 'season', getSeason())); - $: { + run(() => { if (year.toString().length === 4 && $identity.id !== -2 && user) currentPrequels = prequels(user, year, season); - } - $: { + }); + run(() => { if (browser) { $page.url.searchParams.set('year', year.toString()); $page.url.searchParams.set('season', season.toString()); clearAllParameters(['year', 'season']); history.replaceState(null, '', `?${$page.url.searchParams.toString()}`); } - } + }); onMount(() => clearAllParameters(['year', 'season'])); </script> @@ -54,7 +60,7 @@ <Prequels {currentPrequels} /> {/await} - <p /> + <p></p> The count ratio is the number of episodes you've seen of any direct prequels, and the total number of episodes of all direct prequels. diff --git a/src/lib/Tools/Tracker/Tool.svelte b/src/lib/Tools/Tracker/Tool.svelte index 8906e72d..2a1b38d7 100644 --- a/src/lib/Tools/Tracker/Tool.svelte +++ b/src/lib/Tools/Tracker/Tool.svelte @@ -4,14 +4,14 @@ import { onMount } from 'svelte'; import Message from '$lib/Loading/Message.svelte'; - let url = ''; - let title = ''; - let progress = 0; - let error = ''; - let masterList: TrackerEntry[] | null = null; + let url = $state(''); + let title = $state(''); + let progress = $state(0); + let error = $state(''); + let masterList: TrackerEntry[] | null = $state(null); let confirmDelete = 0; - $: listAccess = masterList || []; + let listAccess = $derived(masterList || []); onMount(async () => { masterList = await database.entries.toArray(); @@ -74,9 +74,9 @@ <input type="url" placeholder="URL" bind:value={url} /> <input type="text" placeholder="Title" bind:value={title} /> <input type="number" placeholder="Progress (defaults to 0)" bind:value={progress} /> - <button class="button-lined" on:click={() => addEntry(url, title, progress)}> Add </button> + <button class="button-lined" onclick={() => addEntry(url, title, progress)}> Add </button> - <p /> + <p></p> {#if masterList === null} <Message message="Loading entries ..." /> @@ -95,7 +95,7 @@ type="number" value={entry.progress} size={3} - on:change={(e) => + onchange={(e) => adjustEntry(entry.id, e.target ? e.target.value || entry.progress : entry.progress)} /> @@ -103,17 +103,17 @@ <span class="opaque">|</span> <button class="button-square button-action" - on:click={() => adjustEntry(entry.id, entry.progress - 1)} + onclick={() => adjustEntry(entry.id, entry.progress - 1)} >- </button> <button class="button-square button-action" - on:click={() => adjustEntry(entry.id, entry.progress + 1)} + onclick={() => adjustEntry(entry.id, entry.progress + 1)} > + </button> <span class="opaque">|</span> - <button on:click={() => deleteEntry(entry.id)}>Remove</button> + <button onclick={() => deleteEntry(entry.id)}>Remove</button> </span> </div> </li> diff --git a/src/lib/Tools/UmaMusumeBirthdays.svelte b/src/lib/Tools/UmaMusumeBirthdays.svelte index 29b1faa6..c9b03287 100644 --- a/src/lib/Tools/UmaMusumeBirthdays.svelte +++ b/src/lib/Tools/UmaMusumeBirthdays.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { browser } from '$app/environment'; import { page } from '$app/stores'; import Error from '$lib/Error/RateLimited.svelte'; @@ -23,11 +25,11 @@ 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 umapyoi: Promise<Birthday[]>; + let month = $state(parseOrDefault(urlParameters, 'month', date.getMonth() + 1)); + let day = $state(parseOrDefault(urlParameters, 'day', date.getDate())); + let umapyoi: Promise<Birthday[]> = $state(); - $: { + run(() => { month = Math.min(month, 12); month = Math.max(month, 1); day = Math.min(day, new Date(new Date().getFullYear(), month, 0).getDate()); @@ -39,7 +41,7 @@ clearAllParameters(['month', 'day']); history.replaceState(null, '', `?${$page.url.searchParams.toString()}`); } - } + }); onMount(() => { clearAllParameters(['month', 'day']); diff --git a/src/lib/Tools/Wrapped/ActivityHistory.svelte b/src/lib/Tools/Wrapped/ActivityHistory.svelte index 3da401d4..e652a487 100644 --- a/src/lib/Tools/Wrapped/ActivityHistory.svelte +++ b/src/lib/Tools/Wrapped/ActivityHistory.svelte @@ -3,10 +3,19 @@ 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'; + interface Props { + user: AniListAuthorisation; + activities: ActivityHistoryEntry[]; + year: number; + activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL'; + } + + let { + user, + activities, + year, + activityHistoryPosition + }: Props = $props(); </script> <div diff --git a/src/lib/Tools/Wrapped/Media.svelte b/src/lib/Tools/Wrapped/Media.svelte index ea8a989b..7f8d4f66 100644 --- a/src/lib/Tools/Wrapped/Media.svelte +++ b/src/lib/Tools/Wrapped/Media.svelte @@ -4,14 +4,27 @@ 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; + interface Props { + animeList: Media[] | undefined; + mangaList: Media[] | undefined; + wrapped: Wrapped; + updateWidth: () => void; + highestRatedMediaPercentage: boolean; + highestRatedCount: number; + animeMostTitle: string; + mangaMostTitle: string; + } + + let { + animeList, + mangaList, + wrapped, + updateWidth, + highestRatedMediaPercentage, + highestRatedCount, + animeMostTitle, + mangaMostTitle + }: Props = $props(); </script> {#if animeList !== undefined || mangaList !== undefined} @@ -28,7 +41,7 @@ )} alt="Highest Rated Anime Cover" class="cover-image" - on:load={updateWidth} + onload={updateWidth} /> </a> <div> @@ -67,7 +80,7 @@ )} alt="Highest Rated Manga Cover" class="cover-image" - on:load={updateWidth} + onload={updateWidth} /> </a> <div> diff --git a/src/lib/Tools/Wrapped/MediaExtras.svelte b/src/lib/Tools/Wrapped/MediaExtras.svelte index 9e755ea5..3e083c0b 100644 --- a/src/lib/Tools/Wrapped/MediaExtras.svelte +++ b/src/lib/Tools/Wrapped/MediaExtras.svelte @@ -2,10 +2,19 @@ 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; + interface Props { + topMedia: TopMedia; + updateWidth: () => void; + highestRatedGenreTagPercentage: boolean; + genreTagTitle: string; + } + + let { + topMedia, + updateWidth, + highestRatedGenreTagPercentage, + genreTagTitle + }: Props = $props(); </script> <div class="categories-grid" style="padding-top: 0;"> @@ -22,7 +31,7 @@ src={proxy(topMedia.topGenreMedia.coverImage.extraLarge)} alt="Highest Rated Genre Cover" class="cover-image" - on:load={updateWidth} + onload={updateWidth} /> </a> <div> @@ -53,7 +62,7 @@ src={proxy(topMedia.topTagMedia.coverImage.extraLarge)} alt="Highest Rated Tag Cover" class="cover-image" - on:load={updateWidth} + onload={updateWidth} /> </a> <div> diff --git a/src/lib/Tools/Wrapped/Tool.svelte b/src/lib/Tools/Wrapped/Tool.svelte index 1484ab5c..af817051 100644 --- a/src/lib/Tools/Wrapped/Tool.svelte +++ b/src/lib/Tools/Wrapped/Tool.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import userIdentity from '$stores/identity'; import type { AniListAuthorisation } from '$lib/Data/AniList/identity'; import { onMount } from 'svelte'; @@ -30,153 +32,50 @@ 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); + interface Props { + user: AniListAuthorisation; } - $: { - genreTagCount = genreTagCount; - - if (animeList && mangaList) - topMedia = tops( - [...(animeList || []), ...(mangaList || [])], - genreTagCount, - genreTagsSort, - excludedKeywords - ); - new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth); - } - $: { - excludedKeywords = excludedKeywords; + let { user }: Props = $props(); - if (excludedKeywords.length > 0 && animeList !== undefined && mangaList !== undefined) { - animeList = originalAnimeList; - mangaList = originalMangaList; - animeList = excludeKeywords(animeList as Media[]); - mangaList = excludeKeywords(mangaList as Media[]); - } + const currentYear = new Date(Date.now()).getFullYear(); + let selectedYear = $state(new Date(Date.now()).getFullYear()); + let episodes = $state(0); + let chapters = $state(0); + let minutesWatched = $state(0); + let animeList: Media[] | undefined = $state(undefined); + let mangaList: Media[] | undefined = $state(undefined); + let calculatedAnimeList: Media[] | undefined = $state(undefined); + let calculatedMangaList: Media[] | undefined = $state(undefined); + let originalAnimeList: Media[] | undefined = $state(undefined); + let originalMangaList: Media[] | undefined = $state(undefined); + let transparency = $state(false); + let lightTheme = $state(true); + let watermark = $state(false); + let includeMusic = $state(false); + let includeSpecials = $state(true); + let includeRepeats = $state(false); + let width = $state(1920); + let lightMode = $state(false); + let highestRatedCount = $state(5); + let genreTagCount = $state(5); + let mounted = $state(false); + let generated = $state(false); + let disableActivityHistory = $state(true); + let excludedKeywordsInput = $state(''); + let excludedKeywords: string[] = $state([]); + let useFullActivityHistory = $state(false); + let topGenresTags = $state(true); + let topMedia: TopMedia = $state(); + let highestRatedMediaPercentage = $state(true); + let highestRatedGenreTagPercentage = $state(true); + let genreTagsSort = $state(SortOptions.SCORE); + let mediaSort = $state(SortOptions.SCORE); + let includeMovies = $state(true); + let includeOVAs = $state(true); + let activityHistoryPosition: 'TOP' | 'BELOW_TOP' | 'ORIGINAL' = $state('ORIGINAL'); + let includeOngoingMediaFromPreviousYears = $state(false); - 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; @@ -562,6 +461,113 @@ // return mediaCover(top[Math.floor(Math.random() * top.length)].mediaIds[0]); // }; + run(() => { + 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); + }); + run(() => { + animeList = animeList; + mangaList = mangaList; + highestRatedCount = highestRatedCount; + + new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth); + }); + run(() => { + 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(); + }); + run(() => { + genreTagCount = genreTagCount; + + if (animeList && mangaList) + topMedia = tops( + [...(animeList || []), ...(mangaList || [])], + genreTagCount, + genreTagsSort, + excludedKeywords + ); + + new Promise((resolve) => setTimeout(resolve, 1)).then(updateWidth); + }); + run(() => { + 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()}`); + } + }); + let genreTagTitle = $derived((() => { + switch (genreTagsSort) { + case SortOptions.SCORE: + return 'Highest Rated'; + case SortOptions.MINUTES_WATCHED: + return 'Most Watched'; + case SortOptions.COUNT: + return 'Most Common'; + } + })()); + let animeMostTitle = $derived((() => { + switch (mediaSort) { + case SortOptions.SCORE: + return 'Highest Rated'; + case SortOptions.MINUTES_WATCHED: + return 'Most Watched'; + case SortOptions.COUNT: + return 'Most Common'; + } + })()); + let mangaMostTitle = $derived((() => { + switch (mediaSort) { + case SortOptions.SCORE: + return 'Highest Rated'; + case SortOptions.MINUTES_WATCHED: + return 'Most Read'; + case SortOptions.COUNT: + return 'Most Common'; + } + })()); </script> {#if $userIdentity.id === -2 || user === undefined} @@ -630,10 +636,10 @@ </div> <div class="list"> <div class:card={generated}> - <div id="wrapped-final" /> + <div id="wrapped-final"></div> {#if generated} - <p /> + <p></p> <blockquote style="margin: 0 0 0 1.5rem;"> Click on the image to download, or right click and select "Save Image As...". @@ -642,11 +648,11 @@ </div> {#if generated} - <p /> + <p></p> {/if} <div id="options" class="card"> - <button on:click={screenshot} data-umami-event="Generate Wrapped"> + <button onclick={screenshot} data-umami-event="Generate Wrapped"> Generate image </button> @@ -690,9 +696,9 @@ {/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> + <button onclick={updateWidth}>Find best fit</button> + <button onclick={() => (width -= 25)}>-25px</button> + <button onclick={() => (width += 25)}>+25px</button> Width adjustment<br /> </details> @@ -700,7 +706,7 @@ <summary>Calculation</summary> <input type="checkbox" bind:checked={useFullActivityHistory} /> - Enable full-year activity<button class="smaller-button" on:click={pruneFullYear} + Enable full-year activity<button class="smaller-button" onclick={pruneFullYear} >Refresh data</button > <br /> @@ -732,12 +738,12 @@ <input type="text" bind:value={excludedKeywordsInput} - on:keypress={(e) => { + onkeypress={(e) => { e.key === 'Enter' && submitExcludedKeywords(); }} /> Excluded keywords - <button on:click={submitExcludedKeywords} title="Or click your Enter key" use:tooltip + <button onclick={submitExcludedKeywords} title="Or click your Enter key" use:tooltip >Submit</button > <br /> diff --git a/src/lib/Tools/Wrapped/Top/Activity.svelte b/src/lib/Tools/Wrapped/Top/Activity.svelte index a91bedfb..27dea6a2 100644 --- a/src/lib/Tools/Wrapped/Top/Activity.svelte +++ b/src/lib/Tools/Wrapped/Top/Activity.svelte @@ -4,18 +4,28 @@ 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; + interface Props { + wrapped: Wrapped; + year: number; + activities: ActivityHistoryEntry[]; + useFullActivityHistory: boolean; + updateWidth: () => void; + } + + let { + wrapped, + year, + activities, + useFullActivityHistory, + updateWidth + }: Props = $props(); 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} /> + <img src={proxy(wrapped.avatar.large)} alt="User Avatar" onload={updateWidth} /> </a> <div> <div> diff --git a/src/lib/Tools/Wrapped/Top/Anime.svelte b/src/lib/Tools/Wrapped/Top/Anime.svelte index 08df7fd3..275adadf 100644 --- a/src/lib/Tools/Wrapped/Top/Anime.svelte +++ b/src/lib/Tools/Wrapped/Top/Anime.svelte @@ -1,9 +1,13 @@ <script lang="ts"> import type { Media } from '$lib/Data/AniList/media'; - export let minutesWatched: number; - export let animeList: Media[] | undefined; - export let episodes: number; + interface Props { + minutesWatched: number; + animeList: Media[] | undefined; + episodes: number; + } + + let { minutesWatched, animeList, episodes }: Props = $props(); </script> <div class="category-grid pure-category category top-category"> diff --git a/src/lib/Tools/Wrapped/Top/Manga.svelte b/src/lib/Tools/Wrapped/Top/Manga.svelte index a36f7724..a49d1067 100644 --- a/src/lib/Tools/Wrapped/Top/Manga.svelte +++ b/src/lib/Tools/Wrapped/Top/Manga.svelte @@ -2,8 +2,12 @@ import type { Media } from '$lib/Data/AniList/media'; import { estimatedDayReading } from '$lib/Media/Manga/time'; - export let mangaList: Media[] | undefined; - export let chapters: number; + interface Props { + mangaList: Media[] | undefined; + chapters: number; + } + + let { mangaList, chapters }: Props = $props(); </script> <div class="category-grid pure-category category top-category"> diff --git a/src/lib/Tooltip/LinkedTooltip.svelte b/src/lib/Tooltip/LinkedTooltip.svelte index 6e468cd6..f4546d4c 100644 --- a/src/lib/Tooltip/LinkedTooltip.svelte +++ b/src/lib/Tooltip/LinkedTooltip.svelte @@ -2,23 +2,42 @@ import tooltipPosition from '$stores/tooltipPosition'; import { fade } from 'svelte/transition'; - export let id: string | undefined = undefined; - export let pin: string | undefined = undefined; - export let content: string; - export let disable: boolean = false; - export let pinPosition: 'top' | 'bottom' | 'left' | 'right' = 'top'; - export let offset = 10; - export let tooltipTransitionTime = 200; - export let tooltipHideDelay = 10; - export let debounceDelay = 100; - export let tooltipOpacityTransitionTime = 200; - export let relative = false; - export let ignoreAnchorStyling = false; - - let tooltipDiv: HTMLDivElement | null = null; + interface Props { + id?: string | undefined; + pin?: string | undefined; + content: string; + disable?: boolean; + pinPosition?: 'top' | 'bottom' | 'left' | 'right'; + offset?: number; + tooltipTransitionTime?: number; + tooltipHideDelay?: number; + debounceDelay?: number; + tooltipOpacityTransitionTime?: number; + relative?: boolean; + ignoreAnchorStyling?: boolean; + children?: import('svelte').Snippet; + } + + let { + id = undefined, + pin = undefined, + content, + disable = false, + pinPosition = 'top', + offset = 10, + tooltipTransitionTime = 200, + tooltipHideDelay = 10, + debounceDelay = 100, + tooltipOpacityTransitionTime = 200, + relative = false, + ignoreAnchorStyling = false, + children + }: Props = $props(); + + let tooltipDiv: HTMLDivElement | null = $state(null); let hideTimeout: number | null = null; let debounceTimer: number | null = null; - let opacity = 0; + let opacity = $state(0); const createTooltip = () => { if (!tooltipDiv) { @@ -202,12 +221,12 @@ <span {id} - on:mouseenter={handleMouseEnter} - on:mousemove={handleMouseMove} - on:mouseleave={handleMouseLeave} + onmouseenter={handleMouseEnter} + onmousemove={handleMouseMove} + onmouseleave={handleMouseLeave} role="tooltip" > - <slot /> + {@render children?.()} </span> {#if tooltipDiv} diff --git a/src/lib/User/BadgeWall/AWC.svelte b/src/lib/User/BadgeWall/AWC.svelte index 1cf82a1b..2bd21001 100644 --- a/src/lib/User/BadgeWall/AWC.svelte +++ b/src/lib/User/BadgeWall/AWC.svelte @@ -5,10 +5,19 @@ import FallbackBadge from './FallbackBadge.svelte'; import './badges.css'; - export let awcPromise: Promise<Response>; - export let categoryFilter: string | null; - export let isOwner: boolean; - export let preferences: Preferences; + interface Props { + awcPromise: Promise<Response>; + categoryFilter: string | null; + isOwner: boolean; + preferences: Preferences; + } + + let { + awcPromise, + categoryFilter, + isOwner, + preferences + }: Props = $props(); const awcBadgesGrouped = (awcResponse: string): AWCBadgesGroup[] => { return Array.from( @@ -72,7 +81,7 @@ {group.group} </summary> - <p /> + <p></p> <div class="badges"> {#each group.badges as badge, index} @@ -92,7 +101,7 @@ </div> </details> - <p /> + <p></p> {/each} {/if} {/await} diff --git a/src/lib/User/BadgeWall/BadgePreview.svelte b/src/lib/User/BadgeWall/BadgePreview.svelte index 7a54cbc4..5daccdd2 100644 --- a/src/lib/User/BadgeWall/BadgePreview.svelte +++ b/src/lib/User/BadgeWall/BadgePreview.svelte @@ -1,4 +1,6 @@ <script lang="ts"> + import { run } from 'svelte/legacy'; + import { thumbnail } from '$lib/Utility/image'; import type { Badge } from '$lib/Database/SB/User/badges'; import { cdn } from '$lib/Utility/image'; @@ -8,16 +10,26 @@ import root from '$lib/Utility/root'; import ParallaxImage from '$lib/Image/ParallaxImage.svelte'; - export let selectedBadge: Badge | undefined; - export let onNext: () => void = () => {}; - export let onPrevious: () => void = () => {}; - export let hasNext: boolean; - export let hasPrevious: boolean; + interface Props { + selectedBadge: Badge | undefined; + onNext?: () => void; + onPrevious?: () => void; + hasNext: boolean; + hasPrevious: boolean; + } + + let { + selectedBadge = $bindable(), + onNext = () => {}, + onPrevious = () => {}, + hasNext, + hasPrevious + }: Props = $props(); - let source = cdn(thumbnail(selectedBadge?.image || '')) || ''; + let source = $state(cdn(thumbnail(selectedBadge?.image || '')) || ''); let badgeReference: HTMLImageElement; - $: { + run(() => { if (selectedBadge && selectedBadge.image) { const image = new Image(); @@ -26,14 +38,14 @@ source = image.src; }; } - } + }); - $: { + run(() => { if (selectedBadge) fetch(root(`/api/badges?incrementClickCount=${selectedBadge.id}`), { method: 'PUT' }); - } + }); onMount(() => { badgeReference = document.querySelector('.badge-container-image') as HTMLImageElement; @@ -105,7 +117,7 @@ <div class="badge-preview-badge"> {#if selectedBadge.image} <div role="img" class="badge-container"> - <a href={'#'} on:click={onClick} class="badge-container-image"> + <a href={'#'} onclick={onClick} class="badge-container-image"> <ParallaxImage {source} alternativeText="selectedBadge.description" @@ -115,7 +127,7 @@ </a> </div> - <p /> + <p></p> {/if} </div> @@ -124,7 +136,7 @@ {$locale().dateFormatter(databaseTimeToDate(selectedBadge.time))} {#if (selectedBadge.designer || selectedBadge.source || selectedBadge.post) && !selectedBadge.description} - <p /> + <p></p> {:else if selectedBadge.description} <br /> {/if} @@ -133,7 +145,7 @@ {#if selectedBadge.description} {selectedBadge.description} - <p /> + <p></p> {/if} {#if selectedBadge.designer} @@ -185,7 +197,7 @@ <a href={`?category=${selectedBadge.category}`} - on:click={() => (selectedBadge = undefined)} + onclick={() => (selectedBadge = undefined)} > {selectedBadge.category} </a> @@ -200,11 +212,11 @@ <div class="badge-preview-seek"> {#if hasPrevious} - <button on:click={onPrevious}>Previous</button> + <button onclick={onPrevious}>Previous</button> {/if} {#if hasNext} - <button on:click={onNext} style="float: right;">Next</button> + <button onclick={onNext} style="float: right;">Next</button> {/if} </div> </div> diff --git a/src/lib/User/BadgeWall/Badges.svelte b/src/lib/User/BadgeWall/Badges.svelte index b233d0c3..1acd4cce 100644 --- a/src/lib/User/BadgeWall/Badges.svelte +++ b/src/lib/User/BadgeWall/Badges.svelte @@ -9,19 +9,30 @@ import type { Preferences } from '../../../graphql/$types'; import type { IndexedBadge } from './badge'; - export let ungroupedBadges: IndexedBadge[]; - export let groupedBadges: [string, IndexedBadge[]][]; - export let categoryFilter: string | null; - export let editMode: boolean; - export let preferences: Preferences | undefined; - export let selectedBadge: IndexedBadge | undefined = undefined; + interface Props { + ungroupedBadges: IndexedBadge[]; + groupedBadges: [string, IndexedBadge[]][]; + categoryFilter: string | null; + editMode: boolean; + preferences: Preferences | undefined; + selectedBadge?: IndexedBadge | undefined; + } + + let { + ungroupedBadges, + groupedBadges, + categoryFilter, + editMode, + preferences, + selectedBadge = $bindable(undefined) + }: Props = $props(); </script> {#if ungroupedBadges.length === 0} <div class="card"> No due.moe registered badges found for this user. <a href={'#'} - on:click={(e) => e.preventDefault()} + onclick={(e) => e.preventDefault()} title="This alert does not include AWC badges." use:tooltip>?</a > @@ -36,7 +47,7 @@ <details open={categoryFilter ? categoryFilter === category : true}> <summary>{category}</summary> - <p /> + <p></p> <div class="badges"> {#each badges as badge} @@ -54,7 +65,7 @@ > <a href={`#`} - on:click={() => { + onclick={() => { selectedBadge = badge; const hiddenInput = document.querySelector('input[name="hidden"]'); @@ -93,6 +104,6 @@ </details> {#if groupedBadges[groupedBadges.length - 1][0] !== category} - <p /> + <p></p> {/if} {/each} diff --git a/src/lib/User/BadgeWall/FallbackBadge.svelte b/src/lib/User/BadgeWall/FallbackBadge.svelte index 35a50a7d..68667031 100644 --- a/src/lib/User/BadgeWall/FallbackBadge.svelte +++ b/src/lib/User/BadgeWall/FallbackBadge.svelte @@ -9,22 +9,40 @@ import { dev } from '$app/environment'; import type { Preferences } from '../../../graphql/$types'; - export let source: string | null | undefined; - export let alternative: string | null | undefined; - export let fallback: string | null | undefined; - export let maxReplaceCount = 1; - export let replaceDelay = 1000; - export let error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg'; - export let hideOnError = false; - export let badge: Badge; - export let style = ''; - export let selectedBadge: Badge | null = null; - export let awc = false; - export let index: number | null = null; - export let preferences: Preferences | undefined; - - let replaceCount = 0; - let badgeReference: HTMLImageElement; + interface Props { + source: string | null | undefined; + alternative: string | null | undefined; + fallback: string | null | undefined; + maxReplaceCount?: number; + replaceDelay?: number; + error?: string; + hideOnError?: boolean; + badge: Badge; + style?: string; + selectedBadge?: Badge | null; + awc?: boolean; + index?: number | null; + preferences: Preferences | undefined; + } + + let { + source, + alternative, + fallback, + maxReplaceCount = 1, + replaceDelay = 1000, + error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg', + hideOnError = false, + badge, + style = '', + selectedBadge = $bindable(null), + awc = false, + index = null, + preferences + }: Props = $props(); + + let replaceCount = $state(0); + let badgeReference: HTMLImageElement = $state(); const mouse = tweened( { x: 0, y: 0 }, { @@ -82,9 +100,9 @@ href={awc ? badgeToAny(badge).link : '#'} target="_blank" class="badge-container badge" - on:mousemove={handleMouseMove} - on:mouseleave={handleMouseLeave} - on:click={(e) => { + onmousemove={handleMouseMove} + onmouseleave={handleMouseLeave} + onclick={(e) => { if (!awc) { e.preventDefault(); @@ -100,7 +118,7 @@ bind:this={badgeReference} style="transform: perspective(1000px) rotateX({$mouse.y / 10}deg) rotateY({-$mouse.x / 10}deg); ${style}" - on:error={(e) => delayedReplace(e, fallback)} + onerror={(e) => delayedReplace(e, fallback)} /> </a> </Tooltip> diff --git a/src/lib/Utility/Loading.svelte b/src/lib/Utility/Loading.svelte index 92cbc1ac..3d1eeec6 100644 --- a/src/lib/Utility/Loading.svelte +++ b/src/lib/Utility/Loading.svelte @@ -1,13 +1,23 @@ <script lang="ts"> - export let type: string | undefined = undefined; - export let percent: number | undefined = undefined; - export let card = true; + interface Props { + type?: string | undefined; + percent?: number | undefined; + card?: boolean; + children?: import('svelte').Snippet; + } + + let { + type = undefined, + percent = undefined, + card = true, + children + }: Props = $props(); </script> <div class:card> {#if type} Loading {type} ...{percent ? ` ${percent}%` : ''} {:else} - <slot /> + {@render children?.()} {/if} </div> diff --git a/src/lib/Utility/oauth.ts b/src/lib/Utility/oauth.ts index 78f52bfa..bd824b75 100644 --- a/src/lib/Utility/oauth.ts +++ b/src/lib/Utility/oauth.ts @@ -47,5 +47,5 @@ export const callback = async (options: CallbackOptions) => { } ); - throw redirect(303, options.redirect ?? '/'); + redirect(303, options.redirect ?? '/'); }; |