diff options
Diffstat (limited to 'src/lib/List')
| -rw-r--r-- | src/lib/List/Anime/CleanAnimeList.svelte | 82 | ||||
| -rw-r--r-- | src/lib/List/Anime/Tooltip.svelte | 175 |
2 files changed, 219 insertions, 38 deletions
diff --git a/src/lib/List/Anime/CleanAnimeList.svelte b/src/lib/List/Anime/CleanAnimeList.svelte index 86e2c9c1..04cdccf3 100644 --- a/src/lib/List/Anime/CleanAnimeList.svelte +++ b/src/lib/List/Anime/CleanAnimeList.svelte @@ -16,6 +16,7 @@ import AiringTime from '$lib/Media/Anime/Airing/AiringTime.svelte'; import { browser } from '$app/environment'; import identity from '$stores/identity'; + import Tooltip from './Tooltip.svelte'; export let media: Media[]; export let title: any; @@ -89,52 +90,57 @@ {@const progress = (anime.mediaListEntry || { progress: 0 }).progress} {#if upcoming || notYetReleased || progress !== (anime.nextAiringEpisode?.episode || 9999) - 1} - <div - class="cover-card" - title={anime ? mediaTitle(anime) : undefined} - use:tooltip + <Tooltip id={`anime-${anime.id}`} - data-tooltipPin={`anime-${anime.id}`} + tooltipPin={`anime-${anime.id}`} + content={anime ? mediaTitle(anime) : ''} > - <a href={outboundLink(anime, 'anime', $settings.displayOutboundLinksTo)} target="_blank"> - <img class="cover" src={anime.coverImage.extraLarge} alt="Cover" /> - </a> + <div class="cover-card"> + <a + href={outboundLink(anime, 'anime', $settings.displayOutboundLinksTo)} + target="_blank" + > + <img class="cover" src={anime.coverImage.extraLarge} alt="Cover" /> + </a> - <div class="cover-title"> - {#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={() => { - if (pendingUpdate !== anime.id) { - lastUpdatedMedia = anime.id; - pendingUpdate = anime.id; + <div class="cover-title"> + {#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={() => { + if (pendingUpdate !== anime.id) { + lastUpdatedMedia = anime.id; + pendingUpdate = anime.id; - incrementMediaProgress(anime.id, anime.mediaListEntry?.progress, user, () => { - const mediaListEntry = media.find((m) => m.id === anime.id)?.mediaListEntry; + incrementMediaProgress(anime.id, anime.mediaListEntry?.progress, user, () => { + const mediaListEntry = media.find((m) => m.id === anime.id)?.mediaListEntry; - if (mediaListEntry) mediaListEntry.progress = progress + 1; + if (mediaListEntry) mediaListEntry.progress = progress + 1; - previousAnimeList = media; - animeLists = cleanCache(user, $identity); - pendingUpdate = null; - }); - } - }}>+</button - > - {#if !completed} - [{anime.nextAiringEpisode?.episode === -1 - ? '?' - : (anime.nextAiringEpisode?.episode || 1) - 1}] - <br /> - <AiringTime originalAnime={anime} {subsPlease} /> + previousAnimeList = media; + animeLists = cleanCache(user, $identity); + pendingUpdate = null; + }); + } + }}>+</button + > + {#if !completed} + [{anime.nextAiringEpisode?.episode === -1 + ? '?' + : (anime.nextAiringEpisode?.episode || 1) - 1}] + <br /> + <AiringTime originalAnime={anime} {subsPlease} /> + {/if} + {:else} + <AiringTime originalAnime={anime} {subsPlease} upcoming={true} /> {/if} - {:else} - <AiringTime originalAnime={anime} {subsPlease} upcoming={true} /> - {/if} + </div> </div> - </div> + </Tooltip> {/if} {/each} </div> diff --git a/src/lib/List/Anime/Tooltip.svelte b/src/lib/List/Anime/Tooltip.svelte new file mode 100644 index 00000000..428943a0 --- /dev/null +++ b/src/lib/List/Anime/Tooltip.svelte @@ -0,0 +1,175 @@ +<script lang="ts"> + import tooltipPosition from '$stores/tooltipPosition'; + import { fade } from 'svelte/transition'; + + export let id: string; + export let tooltipPin: string | undefined = undefined; + export let content: string; + export let disable: boolean = false; + + let tooltipDiv: HTMLDivElement | null = null; + const offset = 10; + const tooltipTransitionTime = 200; + const tooltipHideDelay = 10; + const debounceDelay = 100; + const tooltipOpacityTransitionTime = 200; + let hideTimeout: number | null = null; + let debounceTimer: number | null = null; + let opacity = 0; + + const createTooltip = () => { + if (!tooltipDiv) { + tooltipDiv = document.createElement('div'); + + tooltipDiv.style.position = 'absolute'; + tooltipDiv.style.zIndex = '1000'; + opacity = 0; + tooltipDiv.style.pointerEvents = 'none'; + tooltipDiv.style.whiteSpace = 'nowrap'; + tooltipDiv.style.zIndex = '1000'; + + tooltipDiv.classList.add('card'); + tooltipDiv.classList.add('card-small'); + document.body.appendChild(tooltipDiv); + } + }; + + const updateTooltipPosition = (x: number, y: number) => { + if (tooltipDiv) { + if (tooltipPin) { + const pinnedElement = document.getElementById(tooltipPin); + + if (pinnedElement) { + const rect = pinnedElement.getBoundingClientRect(); + const tooltipWidth = tooltipDiv.offsetWidth; + const tooltipHeight = tooltipDiv.offsetHeight; + let top = rect.top - tooltipHeight - offset; + let left = rect.left + rect.width / 2 - tooltipWidth / 2; + + if (left < 0) left = offset; + if (left + tooltipWidth > window.innerWidth) + left = window.innerWidth - tooltipWidth - offset; + if (top < 0) top = rect.top + rect.height + offset; + + tooltipPosition.set({ + x: left, + y: top + window.scrollY + }); + + return; + } + } + + const tooltipWidth = tooltipDiv.offsetWidth; + const tooltipHeight = tooltipDiv.offsetHeight; + let top = y - tooltipHeight - offset; + let left = x - tooltipWidth / 2; + + if (left < 0) left = offset; + if (left + tooltipWidth > window.innerWidth) left = window.innerWidth - tooltipWidth - offset; + if (top < 0) top = y + offset; + + tooltipPosition.set({ + x: left, + y: top + }); + } + }; + + const showTooltip = (content: string, x: number, y: number) => { + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + + hideTimeout = null; + } + + createTooltip(); + + if (tooltipDiv) { + tooltipDiv.innerHTML = content.replace(/\n/g, '<br>'); + + updateTooltipPosition(x, y); + setTimeout(() => { + if (tooltipDiv) { + opacity = 1; + } + }, 10); + } + }; + + const hideTooltip = () => { + setTimeout(() => { + if (tooltipDiv) { + opacity = 0; + + hideTimeout = window.setTimeout(() => { + if (tooltipDiv) { + document.body.removeChild(tooltipDiv); + tooltipDiv = null; + } + }, tooltipTransitionTime); + } + }, tooltipHideDelay); + }; + + const handleMouseEnter = (event: MouseEvent) => { + if (disable) return; + + if (hideTimeout !== null) { + clearTimeout(hideTimeout); + + hideTimeout = null; + } + + if (!tooltipDiv) showTooltip(content, event.pageX, event.pageY); + }; + + const handleMouseMove = (event: MouseEvent) => { + if (debounceTimer !== null) clearTimeout(debounceTimer); + + debounceTimer = window.setTimeout(() => { + if (tooltipDiv && opacity === 1) updateTooltipPosition(event.pageX, event.pageY); + }, debounceDelay); + }; + + const handleMouseLeave = () => { + hideTooltip(); + }; +</script> + +<div + {id} + data-tooltippin={tooltipPin} + on:mouseenter={handleMouseEnter} + on:mousemove={handleMouseMove} + on:mouseleave={handleMouseLeave} + role="tooltip" +> + <slot /> +</div> + +{#if tooltipDiv} + <div + class="tooltip card card-small" + style={`left: ${$tooltipPosition.x}px; top: ${$tooltipPosition.y}px; opacity: ${opacity}; --tooltip-opacity-transition-time: ${tooltipOpacityTransitionTime}ms;`} + > + {#key content} + <span + in:fade={{ duration: tooltipTransitionTime }} + out:fade={{ duration: tooltipTransitionTime }} + > + {@html content} + </span> + {/key} + </div> +{/if} + +<style> + .tooltip { + position: absolute; + z-index: 1000; + transition: opacity var(--tooltip-opacity-transition-time) ease-in-out; + pointer-events: none; + white-space: nowrap; + } +</style> |