LANCommander/LANCommander.Launcher/UI/Pages/Authenticate/Components/ServerSelector.razor
Pat Hartl acb91f8484 Fix login/logout UX
- Moves authentication back to a /Authenticate page route
- Flatten the namespaces of page components
- Implement top-level layouts as windows
- Move handling of redirecting to /Authenticate with component inside of an AuthenticatedLayout
- Use individiual injected services instead of SDK.Client
- Wipe out tokens on logout
- Process logout even if server can't be reached
2025-11-07 20:19:13 -06:00

167 lines
No EOL
5 KiB
Text

@using LANCommander.Launcher.Models
@using LANCommander.SDK.Models
@using ListItem = AntDesign.ListItem
@namespace LANCommander.Launcher.UI.Components
@inject NavigationManager NavigationManager
@inject SDK.Client Client
@inject AuthenticationService AuthenticationService
@inject LocalizationService LocalizationService
<PageHeader>
<TitleTemplate>
@LocalizationService.GetString("Connect")
</TitleTemplate>
</PageHeader>
<Form Model="Model" OnFinish="() => SelectServer(ServerAddress)">
@if (OfflineModeAvailable)
{
<Alert Message="@LocalizationService.GetString("ServerOfflineMessage")" Type="AlertType.Warning" ShowIcon="true" Style="margin-bottom: 16px;" />
}
<FormItem>
<Flex Gap="FlexGap.Small">
<Input @bind-Value="ServerAddress" Placeholder="@LocalizationService.GetString("ServerAddressPlaceholder")" AutoFocus Disabled="Connecting" />
<Button Type="ButtonType.Primary" HtmlType="submit" Disabled="Connecting" Loading="Connecting">@LocalizationService.GetString("Connect")</Button>
@if (OfflineModeAvailable)
{
<Button OnClick="() => OfflineMode()">@LocalizationService.GetString("Offline")</Button>
}
</Flex>
</FormItem>
</Form>
<Divider Text="@LocalizationService.GetString("Discovered")" Orientation="DividerOrientation.Center" />
<AntList DataSource="DiscoveredServers">
<ChildContent>
<ListItem OnClick="() => SelectServer(context.Address)">
<ListItemMeta Title="@context.Name" Description="@context.Address.ToString()"/>
</ListItem>
</ChildContent>
<LoadMore>
<Flex Justify="FlexJustify.Center" Class="load-more">
@if (BeaconActive)
{
<Dropdown Trigger="@(new [] { Trigger.Hover })">
<Overlay>
<Menu>
<MenuItem Key="Cancel" OnClick="CancelBeacon">@LocalizationService.GetString("Cancel")</MenuItem>
</Menu>
</Overlay>
<ChildContent>
<Button Type="ButtonType.Primary" Loading="true" Disabled>@LocalizationService.GetString("Scanning")</Button>
</ChildContent>
</Dropdown>
}
else
{
<Button Type="ButtonType.Primary" OnClick="() => ActivateBeacon()">@LocalizationService.GetString("Rescan")</Button>
}
</Flex>
</LoadMore>
</AntList>
@code {
[Parameter] public EventCallback<Uri> OnSelected { get; set; }
[Parameter] public bool Connecting { get; set; } = false;
bool IsInitializing = false;
bool BeaconActive = false;
bool OfflineModeAvailable = false;
Uri ServerAddress;
AuthRequest Model = new();
List<DiscoveredServer> DiscoveredServers = new();
private CancellationTokenSource DiscoveryToken = new CancellationTokenSource();
protected override async Task OnInitializedAsync()
{
IsInitializing = true;
ServerAddress = Client.Connection.GetServerAddress();
Connecting = true;
OfflineModeAvailable = await AuthenticationService.OfflineModeAvailableAsync();
Client.Beacon.OnBeaconResponse += OnBeaconResponse;
if (!OfflineModeAvailable)
{
ActivateBeacon();
}
Connecting = false;
IsInitializing = false;
}
private void OnBeaconResponse(object sender, BeaconResponseArgs e)
{
var discoveredServer = new DiscoveredServer(e.Message, e.EndPoint);
if (DiscoveredServers.All(s => s.Address != discoveredServer.Address))
DiscoveredServers.Add(discoveredServer);
}
async Task SelectServer(Uri serverAddress)
{
await Client.Beacon.StopProbeAsync();
BeaconActive = false;
if (OnSelected.HasDelegate)
await OnSelected.InvokeAsync(serverAddress);
}
void OfflineMode()
{
AuthenticationService.LoginOffline();
NavigationManager.NavigateTo("/", forceLoad: true);
}
void CancelBeacon()
{
DiscoveryToken?.Cancel();
}
async Task ActivateBeacon()
{
try
{
BeaconActive = true;
await InvokeAsync(StateHasChanged);
// give the UI a chance to render:
await Task.Yield();
if ((DiscoveryToken?.TryReset() ?? false) == false)
{
DiscoveryToken = new();
}
await Client.Beacon.StartProbeAsync(cancellationToken: DiscoveryToken.Token);
await Task.Delay(10000, DiscoveryToken.Token);
await Client.Beacon.StopProbeAsync();
}
catch (OperationCanceledException)
{
// ignore, handled in finally
}
finally
{
Client.Beacon.CleanupProbe();
BeaconActive = false;
await InvokeAsync(StateHasChanged);
}
}
public void Dispose()
{
DiscoveryToken?.Cancel();
DiscoveryToken?.Dispose();
}
}