A UI Razor component for the leaderboard page. It displays best lap times for a selected map, supports a friends-only filter, loads entries asynchronously from LapTimeService, and formats times for display.
@using Sandbox;
@using Sandbox.UI;
@using Sandbox.UI.Navigation;
@using Sandbox.Services;
@using Machines.Components;
@using Machines.Resources;
@using Machines.Systems;
@using System.Linq;
@namespace Machines.UI
@inherits Panel
@attribute [Route( "/leaderboard" )]
@{
var localSteamId = (long)Connection.Local.SteamId;
}
<root class="leaderboard-page @(Input.UsingController ? "controller" : "mouse")">
<div class="eyebrow">BEST LAP TIMES</div>
<div class="page-title">@(_map?.Title ?? "Leaderboard")</div>
<div class="filter-row" onclick=@ToggleFriendsOnly>
<div class="checkbox @(_friendsOnly ? "checked" : "")">
@if ( _friendsOnly )
{
<span class="check-glyph">✓</span>
}
</div>
<div class="filter-label">FRIENDS ONLY</div>
</div>
<div class="board">
@if ( _loading )
{
<div class="board-msg">LOADING…</div>
}
else if ( _entries == null || _entries.Length == 0 )
{
<div class="board-msg">No lap times recorded yet.</div>
}
else
{
@foreach ( var entry in _entries )
{
<div class="row @(entry.SteamId == localSteamId ? "local" : "")">
<div class="row-pos">@entry.Rank</div>
<div class="row-pill">
<div class="row-name">@entry.DisplayName</div>
<div class="row-time">@FormatLap( (float)entry.Value )</div>
</div>
</div>
}
}
</div>
<div class="footer-row">
<div class="back-btn" onclick=@GoBack>
<InputHint Action="MenuBack" Dark=@true class="back-glyph" />
<span class="back-label">BACK</span>
</div>
</div>
</root>
@code
{
private Leaderboards.Board2.Entry[] _entries;
private bool _loading = true;
private bool _friendsOnly;
private MapResource _map;
private string _mapPath;
// Map comes from the ?map= query string, reapplied on every nav.
public override void SetProperty( string name, string value )
{
base.SetProperty( name, value );
if ( name == "map" )
{
_mapPath = value;
_map = ResolveMap( value );
_ = LoadAsync();
}
}
private static MapResource ResolveMap( string resourcePath )
{
if ( string.IsNullOrEmpty( resourcePath ) )
return null;
// Try loaded pool first, fall back to Get.
return ResourceLibrary.GetAll<MapResource>().FirstOrDefault( m => m.ResourcePath == resourcePath )
?? ResourceLibrary.Get<MapResource>( resourcePath );
}
private void ToggleFriendsOnly()
{
_friendsOnly = !_friendsOnly;
_ = LoadAsync();
}
private async Task LoadAsync()
{
_loading = true;
_entries = null;
StateHasChanged();
if ( !string.IsNullOrEmpty( _mapPath ) )
{
try
{
_entries = await LapTimeService.GetLeaderboard( _mapPath, 10, _friendsOnly );
}
catch ( Exception e )
{
Log.Warning( e, "LeaderboardPage: failed to fetch leaderboard" );
}
}
_loading = false;
StateHasChanged();
}
private void GoBack()
{
this.GetNavigator()?.GoBack();
}
public override void Tick()
{
base.Tick();
if ( Input.Pressed( "MenuBack" ) )
GoBack();
}
// Lap times use mm:ss.fff precision.
private static string FormatLap( float seconds )
{
var mins = (int)(seconds / 60f);
var secs = seconds - mins * 60f;
return $"{mins:00}:{secs:00.000}";
}
protected override int BuildHash() =>
System.HashCode.Combine( Input.UsingController, _loading, _entries?.Length ?? 0, _friendsOnly, _mapPath );
}