-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
💄 Basic Home page + Top Players Online (naive) (#61)
* 💄 Basic Home page + Top Players Online (naive) * persist prerendered state
- Loading branch information
Showing
10 changed files
with
419 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3 changes: 3 additions & 0 deletions
3
UI/src/TF2Jump.WebUI/TF2Jump.WebUI.Client/Components/Pages/Index/IndexPage.razor.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
::deep .fluent-messagebar.intent-custom { | ||
animation: none !important; | ||
} |
90 changes: 90 additions & 0 deletions
90
UI/src/TF2Jump.WebUI/TF2Jump.WebUI.Client/Components/Pages/Index/PopularServersWidget.razor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
@using System.Net | ||
@using System.Text.Json.Serialization | ||
@using TempusApi.Models | ||
@using TempusApi.Models.Responses | ||
@using TF2Jump.WebUI.Client.Services | ||
|
||
@implements IDisposable | ||
|
||
<FluentCard> | ||
<FluentStack> | ||
<h3> | ||
Popular Servers | ||
</h3> | ||
<FluentSpacer/> | ||
<FluentAnchor Href="/play/servers" | ||
IconEnd="@(new Icons.Regular.Size16.ArrowRight())"> | ||
More | ||
</FluentAnchor> | ||
</FluentStack> | ||
@if (_filteredServers is not null) | ||
{ | ||
<FluentStack Orientation="@Orientation.Vertical" VerticalGap="8"> | ||
@foreach (var server in _filteredServers) | ||
{ | ||
<FluentMessageBar Title="@server.ServerInfo.Name" | ||
Icon="@(new Icons.Regular.Size16.Server())" | ||
Intent="MessageIntent.Custom" | ||
AllowDismiss="false"> | ||
<FluentSpacer/> | ||
@server.GameInfo.PlayerCount/@server.GameInfo.MaxPlayers online on @server.GameInfo.CurrentMap | ||
<br/> | ||
|
||
<a href="@ServerResolver.GetConnectUri(server, _dnsLookups)">Join</a> | ||
</FluentMessageBar> | ||
} | ||
</FluentStack> | ||
} | ||
</FluentCard> | ||
|
||
@code { | ||
private List<ServerStatusModel>? _servers; | ||
private List<ServerStatusModel>? _filteredServers; | ||
private Dictionary<long, IPAddress> _dnsLookups = []; | ||
private PersistingComponentStateSubscription _persistSubscription; | ||
|
||
[Inject] public required ITempusClient TempusClient { get; set; } | ||
[Inject] public required HttpClient HttpClient { get; set; } | ||
[Inject] public required PersistentComponentState ApplicationState { get; set; } | ||
[Inject] public required ServerResolver ServerResolver { get; set; } | ||
|
||
protected override async Task OnInitializedAsync() | ||
{ | ||
_persistSubscription = ApplicationState.RegisterOnPersisting(PersistData); | ||
|
||
_servers = ApplicationState.TryTakeFromJson<List<ServerStatusModel>>(nameof(_servers), out var servers) | ||
? servers : await TempusClient.GetServersStatusesAsync(); | ||
|
||
if (_servers == null) | ||
{ | ||
throw new Exception("Failed to load server statuses"); | ||
} | ||
|
||
_filteredServers = _servers.Where(x => x.GameInfo is not null) | ||
.OrderByDescending(x => x.GameInfo.PlayerCount) | ||
.Take(5) | ||
.ToList(); | ||
|
||
_dnsLookups = (ApplicationState.TryTakeFromJson<Dictionary<long, IPAddress>>(nameof(_dnsLookups), out var lookups) | ||
? lookups : []) ?? throw new InvalidOperationException(); | ||
|
||
if (lookups is null) | ||
{ | ||
_dnsLookups = await ServerResolver.HydrateDnsLookups(_filteredServers); | ||
} | ||
} | ||
|
||
private Task PersistData() | ||
{ | ||
ApplicationState.PersistAsJson(nameof(_servers), _servers); | ||
ApplicationState.PersistAsJson(nameof(_dnsLookups), _dnsLookups); | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
|
||
public void Dispose() | ||
{ | ||
_persistSubscription.Dispose(); | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
UI/src/TF2Jump.WebUI/TF2Jump.WebUI.Client/Components/Pages/Index/RecentRecordsWidget.razor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
@using Humanizer | ||
@using TempusApi.Models.Responses | ||
|
||
@implements IDisposable | ||
|
||
<FluentCard> | ||
<h3> | ||
<FluentStack> | ||
Recent Records | ||
<FluentSpacer/> | ||
<FluentAnchor Href="/leaderboards/activity" | ||
IconEnd="@(new Icons.Regular.Size16.ArrowRight())"> | ||
More | ||
</FluentAnchor> | ||
</FluentStack> | ||
</h3> | ||
@if (_activity is not null) | ||
{ | ||
<FluentStack Orientation="@Orientation.Vertical" VerticalGap="8"> | ||
@foreach (var wr in _activity.MapRecords.Take(7)) | ||
{ | ||
<FluentMessageBar Title="Map WR" | ||
Icon="@(new Icons.Regular.Size16.Trophy())" | ||
Intent="MessageIntent.Custom" | ||
AllowDismiss="false"> | ||
@wr.PlayerInfo.Name broke @wr.MapInfo.Name @wr.RecordInfo.Date.ToDateTimeOffset().Humanize() | ||
<br/> | ||
WR @wr.RecordInfo.Duration.ToTimeSpan().ToFormattedDuration() | ||
|
||
</FluentMessageBar> | ||
} | ||
</FluentStack> | ||
} | ||
</FluentCard> | ||
|
||
@code { | ||
[Inject] public required ITempusClient TempusClient { get; set; } | ||
[Inject] public required PersistentComponentState ApplicationState { get; set; } | ||
|
||
private RecentActivityModel? _activity; | ||
private PersistingComponentStateSubscription _persistSubscription; | ||
|
||
protected override async Task OnInitializedAsync() | ||
{ | ||
_persistSubscription = ApplicationState.RegisterOnPersisting(PersistData); | ||
|
||
_activity = ApplicationState.TryTakeFromJson<RecentActivityModel>(nameof(_activity), out var restored) | ||
? restored | ||
: await TempusClient.GetRecentActivityAsync(); | ||
} | ||
|
||
private Task PersistData() | ||
{ | ||
ApplicationState.PersistAsJson(nameof(_activity), _activity); | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_persistSubscription.Dispose(); | ||
} | ||
} |
2 changes: 2 additions & 0 deletions
2
...Jump.WebUI/TF2Jump.WebUI.Client/Components/Pages/Leaderboards/Activity/ActivityPage.razor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
@page "/leaderboards/activity" | ||
|
166 changes: 165 additions & 1 deletion
166
UI/src/TF2Jump.WebUI/TF2Jump.WebUI.Client/Components/Pages/Play/TopPlayersOnlinePage.razor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,165 @@ | ||
@page "/play/top-players-online" | ||
@page "/play/top-players-online" | ||
@using System.Net | ||
@using System.Text.Json.Serialization | ||
@using TempusApi.Enums | ||
@using TF2Jump.WebUI.Client.Services | ||
@using TempusApi.Models | ||
|
||
@implements IDisposable | ||
|
||
<PageTitle>Top Players Online</PageTitle> | ||
<HeadContent> | ||
<meta name="description" content="Find players to spectate or chat with, that are currently online on Tempus"/> | ||
</HeadContent> | ||
|
||
<div> | ||
<FluentStack Wrap | ||
VerticalAlignment="VerticalAlignment.Center" | ||
HorizontalAlignment="HorizontalAlignment.Start" | ||
HorizontalGap="16" | ||
VerticalGap="16"> | ||
@foreach(var topPlayer in _topPlayersOnline ?? []) | ||
{ | ||
<FluentCard Width="fit-content" MinimalStyle Class="top-player-card"> | ||
<FluentStack > | ||
<FluentStack HorizontalGap="8" VerticalAlignment="VerticalAlignment.Center" Orientation="Orientation.Vertical"> | ||
<FluentPersona Style="width: 100%" | ||
Status="PresenceStatus.Available" | ||
StatusSize="PresenceBadgeSize.Small" | ||
Image="@(GetSteamProfilePicture(topPlayer.TempusId ?? 0))" | ||
ImageSize="50px"> | ||
<FluentStack VerticalAlignment="VerticalAlignment.Center" HorizontalAlignment="HorizontalAlignment.Center"> | ||
@(topPlayer.RealName ?? topPlayer.SteamName) | ||
|
||
<FluentSpacer/> | ||
|
||
<a href="@ServerResolver.GetConnectUri(new TempusApi.Models.ServerInfo() { Id = topPlayer.ServerInfo.Id??0, Addr = topPlayer.ServerInfo.IpAddress.Split(":")[0], Port = int.Parse(topPlayer.ServerInfo.IpAddress.Split(":")[1])}, _dnsLookups)" style="margin-left: 16px"> | ||
Join | ||
</a> | ||
</FluentStack> | ||
|
||
|
||
</FluentPersona> | ||
<FluentStack VerticalAlignment="VerticalAlignment.Center"> | ||
<ClassIcon Color="white" Size="24" Class="@(@topPlayer.RankClass is 4 ? Class.Demoman : Class.Soldier)"/> | ||
Rank @topPlayer.Rank on @topPlayer.ServerInfo.CurrentMap | ||
|
||
</FluentStack> | ||
<FluentStack VerticalAlignment="VerticalAlignment.Center"> | ||
<FluentIcon Value="@(new Icons.Filled.Size20.Server())" Color="Color.Neutral"/> | ||
@topPlayer.ServerInfo.Alias | ||
</FluentStack> | ||
</FluentStack> | ||
|
||
</FluentStack> | ||
</FluentCard> | ||
} | ||
</FluentStack> | ||
</div> | ||
|
||
@code | ||
{ | ||
private TopPlayerOnlineResult[]? _topPlayersOnline; | ||
[Inject] public required HttpClient HttpClient { get; set; } | ||
[Inject] public required ITempusClient TempusClient { get; set; } | ||
[Inject] public required ServerResolver ServerResolver { get; set; } | ||
[Inject] public required PersistentComponentState ApplicationState { get; set; } | ||
|
||
private Dictionary<long, SteamProfile> _steamProfilePictures = []; | ||
private Dictionary<long, IPAddress> _dnsLookups =[]; | ||
private PersistingComponentStateSubscription _persistSubscription; | ||
|
||
private string GetSteamProfilePicture(long tempusId) | ||
{ | ||
if (_steamProfilePictures.TryGetValue(tempusId, out var profile)) | ||
{ | ||
return profile.Avatars.LargeUrl; | ||
} | ||
|
||
return ""; | ||
} | ||
|
||
protected override async Task OnInitializedAsync() | ||
{ | ||
_persistSubscription = ApplicationState.RegisterOnPersisting(PersistData); | ||
|
||
// TODO: Call our own API to get the top players online | ||
// but API work is in a few weeks | ||
_topPlayersOnline = ApplicationState.TryTakeFromJson<TopPlayerOnlineResult[]>(nameof(_topPlayersOnline), out var restored) | ||
? restored | ||
: await HttpClient.GetFromJsonAsync<TopPlayerOnlineResult[]>("https://tempushub.xyz/api/TopPlayersOnline"); | ||
|
||
_steamProfilePictures = (ApplicationState.TryTakeFromJson<Dictionary<long, SteamProfile>>(nameof(_steamProfilePictures), out var pictures) | ||
? pictures : await HydrateSteamProfilePictures()) ?? throw new InvalidOperationException(); | ||
|
||
_dnsLookups = (ApplicationState.TryTakeFromJson<Dictionary<long, IPAddress>>(nameof(_dnsLookups), out var lookups) | ||
? lookups : await HydrateDnsLookups()) ?? throw new InvalidOperationException(); | ||
} | ||
|
||
private async Task<Dictionary<long, SteamProfile>> HydrateSteamProfilePictures() | ||
{ | ||
if (_topPlayersOnline != null) | ||
{ | ||
var tempusPlayerIds = _topPlayersOnline | ||
.Select(x => x.TempusId) | ||
.Where(x => x is not null) | ||
.Cast<long>(); | ||
|
||
_steamProfilePictures = await TempusClient.GetSteamProfilesAsync(tempusPlayerIds); | ||
return _steamProfilePictures; | ||
} | ||
|
||
return []; | ||
} | ||
|
||
private async Task<Dictionary<long, IPAddress>> HydrateDnsLookups() | ||
{ | ||
var servers = (_topPlayersOnline ?? throw new InvalidOperationException()) | ||
.Select(x => new TempusApi.Models.ServerInfo() | ||
{ | ||
Id = x.ServerInfo.Id??0, | ||
Addr = x.ServerInfo.IpAddress.Split(":")[0], | ||
Name = x.ServerInfo.Name, | ||
Port = int.Parse(x.ServerInfo.IpAddress.Split(":")[1]) | ||
}) | ||
.DistinctBy(x => x.Id) | ||
.ToList(); | ||
|
||
await ServerResolver.HydrateDnsLookups(servers); | ||
|
||
return _dnsLookups; | ||
} | ||
|
||
private Task PersistData() | ||
{ | ||
ApplicationState.PersistAsJson(nameof(_topPlayersOnline), _topPlayersOnline); | ||
ApplicationState.PersistAsJson(nameof(_steamProfilePictures), _steamProfilePictures); | ||
ApplicationState.PersistAsJson(nameof(_dnsLookups), _dnsLookups); | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
public void Dispose() | ||
{ | ||
_persistSubscription.Dispose(); | ||
} | ||
|
||
public record TopPlayerOnlineResult( | ||
[property: JsonPropertyName("steamName")] string SteamName, | ||
[property: JsonPropertyName("realName")] string RealName, | ||
[property: JsonPropertyName("serverInfo")] ServerInfo ServerInfo, | ||
[property: JsonPropertyName("tempusId")] long? TempusId, | ||
[property: JsonPropertyName("rank")] int? Rank, | ||
[property: JsonPropertyName("rankClass")] int? RankClass | ||
); | ||
|
||
public record ServerInfo( | ||
[property: JsonPropertyName("alias")] string Alias, | ||
[property: JsonPropertyName("name")] string Name, | ||
[property: JsonPropertyName("currentPlayers")] int? CurrentPlayers, | ||
[property: JsonPropertyName("maxPlayers")] int? MaxPlayers, | ||
[property: JsonPropertyName("currentMap")] string CurrentMap, | ||
[property: JsonPropertyName("ipAddress")] string IpAddress, | ||
[property: JsonPropertyName("id")] long? Id | ||
); | ||
} |
13 changes: 13 additions & 0 deletions
13
...c/TF2Jump.WebUI/TF2Jump.WebUI.Client/Components/Pages/Play/TopPlayersOnlinePage.razor.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
::deep .fluent-persona { | ||
width: 100%; | ||
} | ||
|
||
::deep .fluent-persona .name { | ||
width: 100%; | ||
} | ||
|
||
::deep .top-player-card { | ||
|
||
flex: 1 0 auto; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.