Improve application responsiveness and background processing
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
import { GET } from '../api/leaderboard/+server.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { LeaderboardResponse } from '$lib/leaderboard.js';
|
||||
|
||||
export async function load({ url }: { url: URL }) {
|
||||
const response = await GET({ url });
|
||||
const body = await response.json();
|
||||
if (!response.ok) error(response.status, body.error);
|
||||
return { leaderboard: body as LeaderboardResponse };
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
let { data: initial }: { data: import('./$types').PageData } = $props();
|
||||
import { timeFormat } from '$ui/stores/time-zone.js';
|
||||
import { onMount, untrack } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { page, navigating } from '$app/stores';
|
||||
const changingFilters = $derived($navigating?.to?.route.id === '/leaderboard');
|
||||
import { leaderboardMetrics, type LeaderboardMetric, type PlayerStatMetric, type LeaderboardEntry, type LeaderboardMap, type LeaderboardResponse } from '$lib/leaderboard.js';
|
||||
import { playerContextHref } from '$lib/leaderboard-navigation.js';
|
||||
import { efficiencyMetrics, isEfficiencyMetric, efficiencyRequirement, efficiencySample, formatEfficiencyValue } from '$lib/leaderboard-efficiency.js';
|
||||
@@ -33,9 +35,9 @@
|
||||
const formatValue = (entry: LeaderboardEntry, key: LeaderboardMetric) => isEfficiencyMetric(key)
|
||||
? formatEfficiencyValue(entry[key], key) : number.format(entry[key]);
|
||||
|
||||
let data = $state<LeaderboardResponse | null>(null);
|
||||
let maps = $state<LeaderboardMap[]>([]);
|
||||
let loading = $state(true);
|
||||
let data = $state<LeaderboardResponse | null>(untrack(() => initial.leaderboard));
|
||||
let maps = $state<LeaderboardMap[]>(untrack(() => initial.leaderboard.maps));
|
||||
let loading = $state(false);
|
||||
let error = $state('');
|
||||
let search = $state('');
|
||||
let mounted = $state(false);
|
||||
@@ -68,8 +70,8 @@
|
||||
|
||||
$effect(() => { search = query; });
|
||||
$effect(() => {
|
||||
const url = apiUrl;
|
||||
if (mounted) untrack(() => { void refresh(url); });
|
||||
const report = initial.leaderboard;
|
||||
untrack(() => { ++requestId; controller?.abort(); data = report; maps = report.maps; loadedUrl = apiUrl; loading = false; error = ''; });
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
@@ -91,7 +93,7 @@
|
||||
controller?.abort();
|
||||
const activeController = new AbortController();
|
||||
controller = activeController;
|
||||
if (url !== loadedUrl) { data = null; checkedAt = null; }
|
||||
if (url !== loadedUrl) checkedAt = null;
|
||||
loading = true;
|
||||
error = '';
|
||||
const timeout = setTimeout(() => activeController.abort(), 20_000);
|
||||
@@ -191,10 +193,10 @@
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<section class="rankings" class:compact={!fullTable} aria-labelledby="rankings-title" aria-busy={loading}>
|
||||
<section class="rankings" class:compact={!fullTable} aria-labelledby="rankings-title" aria-busy={loading || changingFilters}>
|
||||
<div class="rankings-header">
|
||||
<div><h2 id="rankings-title">{metricLabel}</h2><p>{description(metric)}</p>{#if isEfficiencyMetric(metric)}<p>Qualification is measured within the selected game and map. Fleet totals include all recorded players.</p>{/if}</div>
|
||||
<div class="refresh-status" role="status">{loading ? 'Refreshing…' : checkedAt ? `Checked ${$timeFormat.time(checkedAt)}` : ''}<span>Refreshes every 15 seconds</span></div>
|
||||
<div class="refresh-status" role="status">{changingFilters ? 'Updating filters…' : loading ? 'Refreshing…' : checkedAt ? `Checked ${$timeFormat.time(checkedAt)}` : ''}<span>Refreshes every 15 seconds</span></div>
|
||||
</div>
|
||||
{#if data.entries.length}
|
||||
<div class="mobile-view"><button class="text-button" aria-pressed={fullTable} onclick={() => fullTable = !fullTable}>{fullTable ? 'Show selected stat only' : 'Show all stats and player IDs'}</button></div>
|
||||
@@ -227,7 +229,7 @@
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<span>{number.format(data.offset + 1)}–{number.format(data.offset + data.entries.length)} of {number.format(data.total)} {isEfficiencyMetric(metric) ? 'qualifying ' : ''}{data.total === 1 ? 'player' : 'players'}{query ? ` matching “${query}”` : ''}</span>
|
||||
<div><Button size="sm" disabled={data.offset === 0 || loading} onclick={() => updateFilters({ offset: String(Math.max(0, offset - pageSize)) })}>Previous</Button><Button size="sm" disabled={data.offset + data.entries.length >= data.total || loading} onclick={() => updateFilters({ offset: String(offset + pageSize) })}>Next</Button></div>
|
||||
<div><Button size="sm" disabled={data.offset === 0 || loading || changingFilters} onclick={() => updateFilters({ offset: String(Math.max(0, offset - pageSize)) })}>Previous</Button><Button size="sm" disabled={data.offset + data.entries.length >= data.total || loading || changingFilters} onclick={() => updateFilters({ offset: String(offset + pageSize) })}>Next</Button></div>
|
||||
</div>
|
||||
{:else if isEfficiencyMetric(metric)}
|
||||
<EmptyState spacious title="No qualifying players found" description={`${efficiencyRequirement(metric)} in this game/map selection. Try another scope or player search.`}>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { getPlayerProfile } from '$lib/server/fleet/player-profile.js';
|
||||
import { getPlayerLeaderboardEntry, isLeaderboardMetric } from '$lib/server/fleet/leaderboard.js';
|
||||
import { playerReport } from '$lib/server/reports/index.js';
|
||||
import { isLeaderboardMetric } from '$lib/server/fleet/leaderboard.js';
|
||||
import { leaderboardReturnHref } from '$lib/leaderboard-navigation.js';
|
||||
|
||||
export function load({ params, url }: { params: { guid: string }; url: URL }) {
|
||||
export async function load({ params, url }: { params: { guid: string }; url: URL }) {
|
||||
const game = url.searchParams.get('game');
|
||||
const map = url.searchParams.get('map');
|
||||
const requestedMetric = url.searchParams.get('metric');
|
||||
@@ -13,8 +13,8 @@ export function load({ params, url }: { params: { guid: string }; url: URL }) {
|
||||
}
|
||||
const metric = requestedMetric !== null && isLeaderboardMetric(requestedMetric) ? requestedMetric : 'highestRound';
|
||||
const options = { game: game || undefined, map: map || undefined, metric };
|
||||
const profile = getPlayerProfile(params.guid, Number(url.searchParams.get('offset') ?? 0), options);
|
||||
const { profile, entry } = await playerReport(params.guid, Number(url.searchParams.get('offset') ?? 0), options);
|
||||
if (!profile) error(404, 'No recorded stats found for this player');
|
||||
return { profile, returnTo: leaderboardReturnHref(url.searchParams.get('returnTo')),
|
||||
scope: { game: game ?? '', map: map ?? '', metric, entry: getPlayerLeaderboardEntry(params.guid, options) } };
|
||||
scope: { game: game ?? '', map: map ?? '', metric, entry } };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user