head-to-head: add buttons
This commit is contained in:
parent
f2380c2463
commit
0eb11db7d8
@ -88,7 +88,7 @@
|
|||||||
// Toast notification state
|
// Toast notification state
|
||||||
let toastMessage = '';
|
let toastMessage = '';
|
||||||
let showToast = false;
|
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"
|
// Button feedback state - track which buttons are currently "lit up"
|
||||||
let litButtons = new Set<string>();
|
let litButtons = new Set<string>();
|
||||||
@ -543,15 +543,15 @@
|
|||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
title="Open in BeatSaver"
|
title="Open in BeatSaver"
|
||||||
>BSR</a
|
>BS</a
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class="rounded-md border border-white/10 px-2 py-1 text-xs hover:border-white/20 disabled:opacity-50"
|
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}`)}
|
class:lit-up={litButtons.has(`bsr-${item.hash}`)}
|
||||||
on:click={() => { const key = metaByHash[item.hash]?.key; if (key) copyBsrCommand(key, item.hash); }}
|
on:click={() => { const key = metaByHash[item.hash]?.key; if (key) copyBsrCommand(key, item.hash); }}
|
||||||
disabled={!metaByHash[item.hash]?.key}
|
disabled={!metaByHash[item.hash]?.key}
|
||||||
title="Copy !bsr"
|
title="!bsr"
|
||||||
>Copy !bsr</button>
|
>!bsr</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
@ -72,6 +72,55 @@
|
|||||||
let metaByHash: Record<string, MapMeta> = {};
|
let metaByHash: Record<string, MapMeta> = {};
|
||||||
let loadingMeta = false;
|
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 {
|
function normalizeAccuracy(value: number | undefined): number | null {
|
||||||
if (value === undefined || value === null) return null;
|
if (value === undefined || value === null) return null;
|
||||||
return value <= 1 ? value * 100 : value;
|
return value <= 1 ? value * 100 : value;
|
||||||
@ -192,7 +241,8 @@
|
|||||||
if (!bMap.has(key)) continue;
|
if (!bMap.has(key)) continue;
|
||||||
const bScore = bMap.get(key)!;
|
const bScore = bMap.get(key)!;
|
||||||
const [hashLower, diffName, modeName] = key.split('|');
|
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 tA = parseTimeset(aScore.timeset);
|
||||||
const tB = parseTimeset(bScore.timeset);
|
const tB = parseTimeset(bScore.timeset);
|
||||||
const accA = normalizeAccuracy((aScore.accuracy ?? aScore.acc) as number | undefined);
|
const accA = normalizeAccuracy((aScore.accuracy ?? aScore.acc) as number | undefined);
|
||||||
@ -638,11 +688,32 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 flex items-center gap-2">
|
<div class="mt-3 flex items-center gap-2">
|
||||||
<div class="w-1/2">
|
<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
|
<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.leaderboardId}`
|
||||||
: `https://beatleader.com/leaderboard/global/${item.hash}?diff=${encodeURIComponent(item.diffName)}&mode=${encodeURIComponent(item.modeName)}`}
|
: `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>
|
||||||
<div class="w-1/2">
|
<div class="w-1/2">
|
||||||
<SongPlayer hash={item.hash} preferBeatLeader={true} />
|
<SongPlayer hash={item.hash} preferBeatLeader={true} />
|
||||||
@ -663,6 +734,13 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- Toast Notification -->
|
||||||
|
{#if showToast}
|
||||||
|
<div class="toast-notification" role="status" aria-live="polite">
|
||||||
|
{toastMessage}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.text-danger { color: #dc2626; }
|
.text-danger { color: #dc2626; }
|
||||||
|
|
||||||
@ -814,6 +892,54 @@
|
|||||||
background: #0f172a;
|
background: #0f172a;
|
||||||
color: #fff;
|
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>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user