aboutsummaryrefslogtreecommitdiff
path: root/src/lib/List
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/List')
-rw-r--r--src/lib/List/Anime/CleanAnimeList.svelte82
-rw-r--r--src/lib/List/Anime/Tooltip.svelte175
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>