Show import progress in popover

This commit is contained in:
Pat Hartl 2025-02-06 20:54:03 -06:00
parent ba06b56dc3
commit 98bfe90605
10 changed files with 227 additions and 49 deletions

View file

@ -0,0 +1,9 @@
namespace LANCommander.Launcher.Models;
public class ImportProgress
{
public int Index { get; set; }
public int Total { get; set; }
public ImportItem CurrentItem { get; set; }
public bool IsImporting { get; set; }
}

View file

@ -59,6 +59,7 @@ namespace LANCommander.Launcher.Services.Extensions
services.AddSingleton<KeepAliveService>();
#endregion
services.AddSingleton<ImportManagerService>();
services.AddScoped<CollectionService>();
services.AddScoped<CommandLineService>();
services.AddScoped<CompanyService>();

View file

@ -0,0 +1,22 @@
using System.Threading.Tasks;
namespace LANCommander.Launcher.Services;
public class ImportManagerService
{
private readonly ImportService ImportService;
public ImportManagerService(ImportService importService)
{
ImportService = importService;
}
public delegate Task OnImportRequestedHandler();
public event OnImportRequestedHandler OnImportRequested;
public async Task RequestImport()
{
if (OnImportRequested != null)
await OnImportRequested.Invoke();
}
}

View file

@ -40,6 +40,9 @@ namespace LANCommander.Launcher.Services
private readonly Settings Settings;
private readonly DatabaseContext DatabaseContext;
private ImportProgress _importProgress = new();
public ImportProgress Progress => _importProgress;
public delegate Task OnImportUpdatedHandler(ImportStatusUpdate update);
public event OnImportUpdatedHandler OnImportUpdated;
@ -369,9 +372,11 @@ namespace LANCommander.Launcher.Services
{
var library = await LibraryService.GetByUserAsync(AuthenticationService.GetUserId());
Logger?.LogInformation("Importing games");
Logger?.LogInformation("Starting library import");
int i = 1;
_importProgress.IsImporting = true;
_importProgress.Index = 1;
_importProgress.Total = games.Count();
foreach (var game in games)
{
@ -379,13 +384,17 @@ namespace LANCommander.Launcher.Services
{
var remoteGame = await Client.Games.GetAsync(game.Id);
_importProgress.CurrentItem = new ImportItem(game.Id, game.Name);
if (OnImportUpdated != null)
await OnImportUpdated(new ImportStatusUpdate
{
await OnImportUpdated.Invoke(new ImportStatusUpdate
{
CurrentItem = new ImportItem(game.Id, game.Name),
Index = i,
Total = games.Count()
Index = _importProgress.Index,
Total = _importProgress.Total,
CurrentItem = _importProgress.CurrentItem
});
}
if (game != null && remoteGame != null)
{
@ -402,9 +411,13 @@ namespace LANCommander.Launcher.Services
}
finally
{
i++;
_importProgress.Index++;
}
}
_importProgress.IsImporting = false;
if (OnImportComplete != null)
await OnImportComplete.Invoke();
Logger?.LogInformation("Importing games completed");
}

View file

@ -0,0 +1,36 @@
.import-progress {
width: 300px;
&-info {
display: flex;
flex-direction: column;
}
&-status {
position: relative;
white-space: nowrap;
overflow: hidden;
max-width: 100%;
font-size: 14px;
&::after {
content: '';
position: absolute;
right: 0;
top: 0;
height: 100%;
width: 32px;
background: linear-gradient(to right, rgba(31, 31, 31, 0), rgb(31, 31, 31, 1) 90%);
pointer-events: none;
}
}
&-progress-bar {
margin: 4px 0;
}
&-footer {
text-align: center;
font-size: 12px;
}
}

View file

@ -23,4 +23,5 @@
@import '_download-queue';
@import '_game-actions-dialog';
@import '_install-dialog';
@import '_checkbox-button-group';
@import '_checkbox-button-group';
@import '_import';

View file

@ -4,6 +4,7 @@
@inject NavigationManager NavigationManager
@inject IMessageService MessageService
@inject ILogger<LoginForm> Logger
@inject ImportManagerService ImportManagerService
<PageHeader OnBack="OnBack">
<TitleTemplate>
@ -100,9 +101,11 @@
{
await AuthenticationService.Login(Client.GetServerAddress(), Model.UserName, Model.Password);
MainLayout.Import();
await ImportManagerService.RequestImport();
NavigationManager.NavigateTo("/");
MessageService.Success("Welcome back!");
}
catch (Exception ex)
{
@ -128,7 +131,7 @@
{
await AuthenticationService.Login(ServerAddress, token);
MainLayout.Import();
await ImportManagerService.RequestImport();
NavigationManager.NavigateTo("/");
}

View file

@ -4,6 +4,7 @@
@inject NavigationManager NavigationManager
@inject IMessageService MessageService
@inject ILogger<RegistrationForm> Logger
@inject ImportManagerService ImportManagerService
<PageHeader OnBack="OnBack">
<TitleTemplate>
@ -54,7 +55,7 @@
{
await AuthenticationService.Register(Client.GetServerAddress(), Model.UserName, Model.Password, Model.PasswordConfirmation);
MainLayout.Import();
await ImportManagerService.RequestImport();
NavigationManager.NavigateTo("/");
}
@ -75,7 +76,7 @@
{
await AuthenticationService.Login(ServerAddress, token);
MainLayout.Import();
await ImportManagerService.RequestImport();
NavigationManager.NavigateTo("/");
}

View file

@ -0,0 +1,37 @@
@using LANCommander.Launcher.Models
@inject ImportService ImportService
<div class="import-progress">
<div class="import-progress-container">
<Flex Vertical>
<div class="import-progress-info">
<div class="import-progress-status">
@if (Total > 0)
{
<Text>Importing: @(CurrentItem?.Name ?? "")</Text>
}
else
{
<Text>Starting import...</Text>
}
</div>
<div class="import-progress-progress-bar">
<Progress
Percent="@((double)(Index * 100) / Total)"
Status="ProgressStatus.Active"
ShowInfo="false"
/>
</div>
<div class="import-progress-footer">
@Index / @Total
</div>
</div>
</Flex>
</div>
</div>
@code {
[Parameter] public int Index { get; set; }
[Parameter] public int Total { get; set; }
[Parameter] public ImportItem CurrentItem { get; set; }
}

View file

@ -12,6 +12,8 @@
@inject ModalService ModalService
@inject LANCommander.SDK.Client LANCommander
@inject IJSRuntime JS
@inject ILogger<MainLayout> Logger
@inject ImportManagerService ImportManagerService
<CustomWindow HeaderHeight="37">
<HeaderExtraControlsLayout>
@ -19,14 +21,25 @@
<AuthenticatedView>
<Authenticated>
<SpaceItem>
<Popover Placement="Placement.BottomRight" IsButton OnClick="Import" Trigger="new[] { Trigger.Hover }">
<ChildContent>
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Sync" Loading="@Importing"/>
</ChildContent>
<ContentTemplate>
<Progress Percent="@((ImportStatusIndex / (float)ImportStatusTotal) * 100)" Steps="@ImportStatusTotal"/>
</ContentTemplate>
</Popover>
@if (Importing)
{
<Popover Placement="Placement.BottomRight" IsButton Trigger="new[] { Trigger.Hover }">
<ChildContent>
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Sync" Loading="@Importing"/>
</ChildContent>
<ContentTemplate>
<ImportProgressDisplay
Index="@ImportStatusIndex"
Total="@ImportStatusTotal"
CurrentItem="@(_importProgress?.CurrentItem)"
/>
</ContentTemplate>
</Popover>
}
else
{
<Button Type="@ButtonType.Text" Icon="@IconType.Outline.Sync" OnClick="@Import"/>
}
</SpaceItem>
<SpaceItem>
@ -82,8 +95,6 @@
public IMessageService Messages { get; set; }
public static MainLayout _MainLayout { get; set; }
string RandomQuip = "";
string[] CrashQuips = new[]
@ -120,38 +131,44 @@
int ImportStatusIndex = 0;
int ImportStatusTotal = 0;
private ImportProgress _importProgress => ImportService.Progress;
protected override async Task OnInitializedAsync()
{
_MainLayout = this;
Settings = SettingService.GetSettings();
Messages = MessageService;
ImportService.OnImportComplete += OnImportComplete;
ImportService.OnImportUpdated += OnImportUpdated;
ImportManagerService.OnImportRequested += Import;
AuthenticationService.OnOfflineModeChanged += OnOfflineModeChanged;
var randIndex = new Random().Next(0, CrashQuips.Length - 1);
RandomQuip = CrashQuips[randIndex];
}
async Task OnImportComplete()
public void Dispose()
{
MessageService.Success("Import Complete", 3);
}
if (AuthenticationService != null)
{
AuthenticationService.OnOfflineModeChanged -= OnOfflineModeChanged;
}
async Task OnImportUpdated(ImportStatusUpdate status)
{
MessageService.Info($"Importing {status.CurrentItem.Name}");
if (ImportService != null)
{
ImportService.OnImportComplete -= OnImportCompleteHandler;
ImportService.OnImportUpdated -= OnImportUpdatedHandler;
}
if (ImportManagerService != null)
{
ImportManagerService.OnImportRequested -= Import;
}
}
async void OnOfflineModeChanged(bool state)
{
_MainLayout.ConnectionState.OfflineModeEnabled = state;
_MainLayout.ConnectionState.IsConnected = LANCommander.IsConnected();
await InvokeAsync(_MainLayout.StateHasChanged);
ConnectionState.OfflineModeEnabled = state;
ConnectionState.IsConnected = LANCommander.IsConnected();
await InvokeAsync(StateHasChanged);
}
async Task CopyError(Exception ex)
@ -222,23 +239,61 @@
NavigationManager.NavigateTo("/Authenticate");
}
public static async void Import()
public async Task Import()
{
if (!_MainLayout.Importing)
if (!Importing)
{
await _MainLayout.JS.InvokeVoidAsync("window.external.sendMessage", "import");
Logger?.LogInformation("Starting import process");
_MainLayout.Importing = true;
_MainLayout.StateHasChanged();
_MainLayout.MessageService.Info("Import Started", 2.5);
// Clear any existing handlers
ImportService.OnImportComplete -= OnImportCompleteHandler;
ImportService.OnImportUpdated -= OnImportUpdatedHandler;
// Add our handlers
ImportService.OnImportComplete += OnImportCompleteHandler;
ImportService.OnImportUpdated += OnImportUpdatedHandler;
// Now start the import process
Importing = true;
ImportStatusIndex = 0;
ImportStatusTotal = 0;
await InvokeAsync(StateHasChanged);
MessageService.Info("Import Started", 2.5);
Logger?.LogInformation("Starting import");
await ImportService.ImportLibraryAsync(); // Call directly instead of using JS
}
}
[JSInvokable("ImportComplete")]
public static void ImportComplete()
private async Task OnImportCompleteHandler()
{
_MainLayout.Importing = false;
_MainLayout.StateHasChanged();
_MainLayout.ImportService.ImportHasCompleted();
Logger?.LogInformation("Import Complete handler called");
Importing = false;
ImportStatusIndex = 0;
ImportStatusTotal = 0;
MessageService.Success("Import Complete", 3);
await InvokeAsync(StateHasChanged);
}
}
private async Task OnImportUpdatedHandler(ImportStatusUpdate status)
{
Logger?.LogInformation("Import Update handler called: {Index}/{Total}",
status.Index, status.Total);
ImportStatusIndex = status.Index;
ImportStatusTotal = status.Total;
await InvokeAsync(StateHasChanged);
}
}
<style>
.import-progress {
width: 300px; /* Fixed width */
}
.import-progress .downloader-current-upper-status {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 100%;
}
</style>