head-to-head: add buttons
This commit is contained in:
parent
f2380c2463
commit
0eb11db7d8
@ -88,7 +88,7 @@
|
||||
// Toast notification state
|
||||
let toastMessage = '';
|
||||
let showToast = false;
|
||||
let toastTimeout: number | null = null;
|
||||
let toastTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
// Button feedback state - track which buttons are currently "lit up"
|
||||
let litButtons = new Set<string>();
|
||||
@ -543,15 +543,15 @@
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
title="Open in BeatSaver"
|
||||
>BSR</a
|
||||
>BS</a
|
||||
>
|
||||
<button
|
||||
class="rounded-md border border-white/10 px-2 py-1 text-xs hover:border-white/20 disabled:opacity-50"
|
||||
class:lit-up={litButtons.has(`bsr-${item.hash}`)}
|
||||
on:click={() => { const key = metaByHash[item.hash]?.key; if (key) copyBsrCommand(key, item.hash); }}
|
||||
disabled={!metaByHash[item.hash]?.key}
|
||||
title="Copy !bsr"
|
||||
>Copy !bsr</button>
|
||||
title="!bsr"
|
||||
>!bsr</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
@ -72,6 +72,55 @@
|
||||
let metaByHash: Record<string, MapMeta> = {};
|
||||
let loadingMeta = false;
|
||||
|
||||
// Toast notification state
|
||||
let toastMessage = '';
|
||||
let showToast = false;
|
||||
let toastTimeout: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
// Button feedback state - track which buttons are currently "lit up"
|
||||
let litButtons = new Set<string>();
|
||||
|
||||
function showToastMessage(message: string) {
|
||||
// Clear any existing toast
|
||||
if (toastTimeout) {
|
||||
clearTimeout(toastTimeout);
|
||||
}
|
||||
|
||||
toastMessage = message;
|
||||
showToast = true;
|
||||
|
||||
// Auto-hide toast after 3 seconds
|
||||
toastTimeout = setTimeout(() => {
|
||||
showToast = false;
|
||||
toastMessage = '';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function lightUpButton(buttonId: string) {
|
||||
litButtons.add(buttonId);
|
||||
// Reassign here too to trigger reactivity immediately
|
||||
litButtons = litButtons;
|
||||
// Remove the lighting effect after 1 second
|
||||
setTimeout(() => {
|
||||
litButtons.delete(buttonId);
|
||||
litButtons = litButtons; // Trigger reactivity
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
async function copyBsrCommand(key: string, hash: string) {
|
||||
try {
|
||||
const bsrCommand = `!bsr ${key}`;
|
||||
await navigator.clipboard.writeText(bsrCommand);
|
||||
|
||||
// Show success feedback with the actual command
|
||||
showToastMessage(`Copied "${bsrCommand}" to clipboard`);
|
||||
lightUpButton(`bsr-${hash}`);
|
||||
} catch (err) {
|
||||
console.error('Failed to copy to clipboard:', err);
|
||||
showToastMessage('Failed to copy to clipboard');
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeAccuracy(value: number | undefined): number | null {
|
||||
if (value === undefined || value === null) return null;
|
||||
return value <= 1 ? value * 100 : value;
|
||||
@ -192,7 +241,8 @@
|
||||
if (!bMap.has(key)) continue;
|
||||
const bScore = bMap.get(key)!;
|
||||
const [hashLower, diffName, modeName] = key.split('|');
|
||||
const hash = (aScore.leaderboard?.song?.hash ?? bScore.leaderboard?.song?.hash ?? hashLower).toString();
|
||||
const rawHash = (aScore.leaderboard?.song?.hash ?? bScore.leaderboard?.song?.hash ?? hashLower);
|
||||
const hash = String(rawHash).toLowerCase();
|
||||
const tA = parseTimeset(aScore.timeset);
|
||||
const tB = parseTimeset(bScore.timeset);
|
||||
const accA = normalizeAccuracy((aScore.accuracy ?? aScore.acc) as number | undefined);
|
||||
@ -638,11 +688,32 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-2">
|
||||
<div class="w-1/2">
|
||||
<a class="rounded-md border border-white/10 px-2 py-1 text-xs hover:border-white/20" href={item.leaderboardId
|
||||
<div class="w-1/2 flex flex-wrap gap-2">
|
||||
<a
|
||||
class="rounded-md border border-white/10 px-2 py-1 text-xs hover:border-white/20"
|
||||
href={item.leaderboardId
|
||||
? `https://beatleader.com/leaderboard/global/${item.leaderboardId}`
|
||||
: `https://beatleader.com/leaderboard/global/${item.hash}?diff=${encodeURIComponent(item.diffName)}&mode=${encodeURIComponent(item.modeName)}`}
|
||||
target="_blank" rel="noopener" title="Open in BeatLeader">BL</a>
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
title="Open in BeatLeader"
|
||||
>BL</a
|
||||
>
|
||||
<a
|
||||
class="rounded-md border border-white/10 px-2 py-1 text-xs hover:border-white/20"
|
||||
href={metaByHash[item.hash]?.key ? `https://beatsaver.com/maps/${metaByHash[item.hash]?.key}` : `https://beatsaver.com/search/hash/${item.hash}`}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
title="Open in BeatSaver"
|
||||
>BS</a
|
||||
>
|
||||
<button
|
||||
class="rounded-md border border-white/10 px-2 py-1 text-xs hover:border-white/20 disabled:opacity-50"
|
||||
class:lit-up={litButtons.has(`bsr-${item.hash}`)}
|
||||
on:click={() => { const key = metaByHash[item.hash]?.key; if (key) copyBsrCommand(key, item.hash); }}
|
||||
disabled={!metaByHash[item.hash]?.key}
|
||||
title="!bsr"
|
||||
>!bsr</button>
|
||||
</div>
|
||||
<div class="w-1/2">
|
||||
<SongPlayer hash={item.hash} preferBeatLeader={true} />
|
||||
@ -663,6 +734,13 @@
|
||||
{/if}
|
||||
</section>
|
||||
|
||||
<!-- Toast Notification -->
|
||||
{#if showToast}
|
||||
<div class="toast-notification" role="status" aria-live="polite">
|
||||
{toastMessage}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.text-danger { color: #dc2626; }
|
||||
|
||||
@ -814,6 +892,54 @@
|
||||
background: #0f172a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Toast notification styles */
|
||||
.toast-notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #10b981;
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||||
z-index: 1000;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Button lighting effect */
|
||||
.lit-up {
|
||||
background: linear-gradient(45deg, #10b981, #059669);
|
||||
border-color: #10b981;
|
||||
color: white;
|
||||
box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
|
||||
animation: pulse 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
|
||||
}
|
||||
70% {
|
||||
box-shadow: 0 0 0 10px rgba(16, 185, 129, 0);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user