+11
-2
frontend/src/lib/components/LikersTooltip.svelte
+11
-2
frontend/src/lib/components/LikersTooltip.svelte
···
1
1
<script lang="ts">
2
2
import { API_URL } from '$lib/config';
3
+
import SensitiveImage from './SensitiveImage.svelte';
3
4
4
5
interface Liker {
5
6
did: string;
···
12
13
interface Props {
13
14
trackId: number;
14
15
likeCount: number;
16
+
onMouseEnter?: () => void;
17
+
onMouseLeave?: () => void;
15
18
}
16
19
17
-
let { trackId, likeCount }: Props = $props();
20
+
let { trackId, likeCount, onMouseEnter, onMouseLeave }: Props = $props();
18
21
19
22
let likers = $state<Liker[]>([]);
20
23
let loading = $state(true); // start as loading
···
67
70
<div
68
71
class="likers-tooltip"
69
72
role="tooltip"
73
+
onmouseenter={onMouseEnter}
74
+
onmouseleave={onMouseLeave}
70
75
>
71
76
{#if loading}
72
77
<div class="loading">loading...</div>
···
80
85
class="liker"
81
86
>
82
87
{#if liker.avatar_url}
83
-
<img src={liker.avatar_url} alt={liker.display_name} class="avatar" />
88
+
<SensitiveImage src={liker.avatar_url}>
89
+
<img src={liker.avatar_url} alt={liker.display_name} class="avatar" />
90
+
</SensitiveImage>
84
91
{:else}
85
92
<div class="avatar-placeholder">
86
93
{liker.display_name.charAt(0).toUpperCase()}
···
134
141
display: flex;
135
142
flex-direction: column;
136
143
gap: 0.5rem;
144
+
max-height: 240px;
145
+
overflow-y: auto;
137
146
}
138
147
139
148
.liker {
+21
-3
frontend/src/lib/components/TrackItem.svelte
+21
-3
frontend/src/lib/components/TrackItem.svelte
···
75
75
toast.success(`queued ${track.title}`, 1800);
76
76
}
77
77
78
+
let likersTooltipTimeout: ReturnType<typeof setTimeout> | null = null;
79
+
78
80
function handleLikesMouseEnter() {
79
-
// show tooltip immediately, fetch will handle its own delay
81
+
// cancel any pending close
82
+
if (likersTooltipTimeout) {
83
+
clearTimeout(likersTooltipTimeout);
84
+
likersTooltipTimeout = null;
85
+
}
80
86
showLikersTooltip = true;
81
87
}
82
88
83
89
function handleLikesMouseLeave() {
84
-
showLikersTooltip = false;
90
+
// delay closing to allow moving into the tooltip
91
+
likersTooltipTimeout = setTimeout(() => {
92
+
showLikersTooltip = false;
93
+
likersTooltipTimeout = null;
94
+
}, 150);
85
95
}
86
96
87
97
function handleLikesKeydown(event: KeyboardEvent) {
88
98
if (event.key === 'Enter' || event.key === ' ') {
89
99
event.preventDefault();
90
100
showLikersTooltip = true;
101
+
}
102
+
if (event.key === 'Escape') {
103
+
showLikersTooltip = false;
91
104
}
92
105
}
93
106
···
223
236
>
224
237
{likeCount} {likeCount === 1 ? 'like' : 'likes'}
225
238
{#if showLikersTooltip}
226
-
<LikersTooltip trackId={track.id} likeCount={likeCount} />
239
+
<LikersTooltip
240
+
trackId={track.id}
241
+
likeCount={likeCount}
242
+
onMouseEnter={handleLikesMouseEnter}
243
+
onMouseLeave={handleLikesMouseLeave}
244
+
/>
227
245
{/if}
228
246
</span>
229
247
{/if}