add pick button and link mappool map to osu map page

This commit is contained in:
2026-03-06 18:02:42 +01:00
parent 511740faa5
commit 093d9fd83c

View File

@@ -59,13 +59,13 @@
// Group mappool slots by category // Group mappool slots by category
const groupedSlots = $derived(() => { const groupedSlots = $derived(() => {
if (!m.mappool?.slots) return {}; if (!m.mappool?.slots) return {};
const groups: Record<string, any[]> = {}; const groups: Record<string, unknown[]> = {};
for (const slot of m.mappool.slots) { for (const slot of m.mappool.slots) {
if (!groups[slot.category]) groups[slot.category] = []; if (!groups[slot.category]) groups[slot.category] = [];
groups[slot.category].push(slot); groups[slot.category].push(slot);
} }
for (const cat of Object.keys(groups)) { for (const cat of Object.keys(groups)) {
groups[cat].sort((a: any, b: any) => a.orderInCategory - b.orderInCategory); groups[cat].sort((a: unknown, b: unknown) => a.orderInCategory - b.orderInCategory);
} }
// Return entries sorted by canonical mod order // Return entries sorted by canonical mod order
return Object.fromEntries( return Object.fromEntries(
@@ -99,7 +99,7 @@
const mappoolAvgSR = $derived(() => { const mappoolAvgSR = $derived(() => {
const slots = m.mappool?.slots; const slots = m.mappool?.slots;
if (!slots?.length) return null; if (!slots?.length) return null;
const avg = slots.reduce((sum: number, s: any) => sum + (s.starRating ?? 0), 0) / slots.length; const avg = slots.reduce((sum: number, s: unknown) => sum + (s.starRating ?? 0), 0) / slots.length;
return avg.toFixed(2); return avg.toFixed(2);
}); });
@@ -412,26 +412,58 @@
{#if !mappoolCollapsed} {#if !mappoolCollapsed}
<div class="mt-3 flex flex-col gap-3"> <div class="mt-3 flex flex-col gap-3">
{#each Object.entries(groupedSlots()) as [category, slots]} {#each Object.entries(groupedSlots()) as [category, slots] (category)}
<!-- Grouped slots -->
<div> <div>
<span class="mb-1.5 inline-block rounded border px-2 py-0.5 text-xs font-600 {catColors[category] ?? 'border-border'}"> <span class="mb-1.5 inline-block rounded border px-2 py-0.5 text-xs font-600 {catColors[category] ?? 'border-border'}">
{category} {category}
</span> </span>
<div class="grid grid-cols-1 gap-1.5 sm:grid-cols-2"> <div class="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
{#each slots as slot} {#each slots as slot (slot.id)}
<!-- Slot -->
{@const bm = data.beatmapCache[slot.beatmapId]} {@const bm = data.beatmapCache[slot.beatmapId]}
{@const isPlayed = playedSlotIds.has(slot.id)} {@const isPlayed = playedSlotIds.has(slot.id)}
{@const isTB = slot.category === 'TB'} {@const isTB = slot.category === 'TB'}
{@const tbLocked = isTB && !isTiebreakerAllowed} {@const tbLocked = isTB && !isTiebreakerAllowed}
{@const cantPick = !isMyTurnToPick || !pickingPhase} {@const cantPick = !isMyTurnToPick || !pickingPhase}
<div class="flex items-center gap-2 rounded-lg border p-2 transition-all {isPlayed
? 'border-border/50 bg-surface-900 opacity-30'
: tbLocked
? 'border-border/50 bg-surface-900 opacity-30'
: 'border-border bg-surface-800'}">
<a
href="https://osu.ppy.sh/beatmaps/{slot.beatmapId}"
target="_blank"
class="flex min-w-0 flex-1 items-center gap-3"
>
{#if bm?.listCoverUrl}
<img src={bm.listCoverUrl} alt="" class="h-10 w-20 shrink-0 rounded object-cover" />
{:else}
<div class="flex h-10 w-20 shrink-0 items-center justify-center rounded bg-surface-600 text-xs text-text-secondary">?</div>
{/if}
<div class="min-w-0 flex-1">
{#if bm}
<p class="truncate text-sm font-500">{bm.artist} - {bm.title}</p>
<p class="text-xs text-text-secondary">[{bm.version}] &middot; {bm.starRating.toFixed(2)}★ &middot; {bm.bpm}bpm</p>
{:else}
<p class="text-xs text-text-secondary">#{slot.beatmapId}</p>
{/if}
</div>
</a>
<span class="text-xs font-600 text-text-secondary">{category}{slot.orderInCategory}</span>
{#if isPlayed}
<span class="text-xs text-text-secondary">✓</span>
{:else if tbLocked}
<span class="text-[10px] text-pink-400/60" title="Match point only">🔒</span>
{:else if pickingPhase}
<form method="post" action="?/pick" use:enhance={() => { <form method="post" action="?/pick" use:enhance={() => {
picking = true; picking = true;
pickError = ''; pickError = '';
return async ({ result, update }) => { return async ({ result, update }) => {
picking = false; picking = false;
if (result.type === 'failure' || (result.type === 'success' && (result.data as any)?.error)) { if (result.type === 'failure' || (result.type === 'success' && (result.data as Record<string, unknown>)?.error)) {
pickError = (result.data as any)?.error ?? 'Pick failed'; pickError = (result.data as Record<string, unknown>)?.error as string ?? 'Pick failed';
} else { } else {
await update(); await update();
} }
@@ -440,36 +472,16 @@
<input type="hidden" name="slotId" value={slot.id} /> <input type="hidden" name="slotId" value={slot.id} />
<button <button
type="submit" type="submit"
disabled={isPlayed || picking || tbLocked || cantPick} disabled={picking || cantPick}
class="flex w-full items-center gap-3 rounded-lg border p-2 text-left transition-all {isPlayed class="rounded px-2 py-1 text-[10px] font-700 transition-colors {cantPick
? 'border-border/50 bg-surface-900 opacity-30 cursor-not-allowed' ? 'bg-surface-600 text-text-secondary/40 cursor-default'
: tbLocked : 'bg-white text-black hover:bg-white/80 cursor-pointer'}"
? 'border-border/50 bg-surface-900 opacity-30 cursor-not-allowed'
: cantPick
? 'border-border bg-surface-800 opacity-60 cursor-default'
: 'border-border bg-surface-700 hover:border-accent hover:bg-surface-600 cursor-pointer'}"
> >
{#if bm?.listCoverUrl} PICK
<img src={bm.listCoverUrl} alt="" class="h-10 w-20 rounded object-cover" />
{:else}
<div class="flex h-10 w-20 items-center justify-center rounded bg-surface-600 text-xs text-text-secondary">?</div>
{/if}
<div class="min-w-0 flex-1">
{#if bm}
<p class="truncate text-xs font-500">{bm.artist} - {bm.title}</p>
<p class="text-xs text-text-secondary">[{bm.version}] &middot; {bm.starRating.toFixed(2)}★ &middot; {bm.bpm}bpm</p>
{:else}
<p class="text-xs text-text-secondary">#{slot.beatmapId}</p>
{/if}
</div>
<span class="text-xs font-600 text-text-secondary">{category}{slot.orderInCategory}</span>
{#if isPlayed}
<span class="text-xs text-text-secondary">✓</span>
{:else if tbLocked}
<span class="text-[10px] text-pink-400/60" title="Match point only">🔒</span>
{/if}
</button> </button>
</form> </form>
{/if}
</div>
{/each} {/each}
</div> </div>
</div> </div>