From 37ca8fecf364758fff95d33b9a5d10252195e64b Mon Sep 17 00:00:00 2001 From: Cinka Date: Sun, 22 Jun 2025 10:40:42 +0300 Subject: [PATCH] - tweak: Rework loading status --- .idea/.idea.Nebula/.idea/avalonia.xml | 1 + .../Controls/ServerListView.axaml.cs | 10 ++- .../FavoriteServerListProvider.cs | 30 ++++--- .../HubServerListProvider.cs | 4 +- .../IServerListProvider.cs | 3 +- .../ServerListProviders/TestServerList.cs | 3 +- .../Pages/ConfigurationViewModel.cs | 6 -- .../Pages/ContentBrowserViewModel.cs | 2 - .../ViewModels/Pages/ServerOverviewModel.cs | 35 ++++++-- .../ServerCompoundEntryModelView.cs | 89 +++++++++++++++++++ .../ViewModels/ServerEntryModelView.cs | 38 ++------ .../Views/ServerCompoundEntryView.axaml | 73 +++++++++++++++ .../Views/ServerCompoundEntryView.axaml.cs | 17 ++++ Nebula.Shared/Services/RestService.cs | 7 ++ Nebula.sln.DotSettings.user | 3 + 15 files changed, 251 insertions(+), 70 deletions(-) create mode 100644 Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs create mode 100644 Nebula.Launcher/Views/ServerCompoundEntryView.axaml create mode 100644 Nebula.Launcher/Views/ServerCompoundEntryView.axaml.cs diff --git a/.idea/.idea.Nebula/.idea/avalonia.xml b/.idea/.idea.Nebula/.idea/avalonia.xml index 1aea4d7..50bc6a6 100644 --- a/.idea/.idea.Nebula/.idea/avalonia.xml +++ b/.idea/.idea.Nebula/.idea/avalonia.xml @@ -35,6 +35,7 @@ + diff --git a/Nebula.Launcher/Controls/ServerListView.axaml.cs b/Nebula.Launcher/Controls/ServerListView.axaml.cs index 4eaf9b8..085e514 100644 --- a/Nebula.Launcher/Controls/ServerListView.axaml.cs +++ b/Nebula.Launcher/Controls/ServerListView.axaml.cs @@ -49,7 +49,7 @@ public partial class ServerListView : UserControl { if (rawView is ServerEntryModelView serverEntryModelView) { - serverEntryModelView.UpdateStatusIfNecessary(); + //serverEntryModelView.UpdateStatusIfNecessary(); } } } @@ -61,9 +61,10 @@ public partial class ServerListView : UserControl if(IsLoading) return; - foreach (IFilterConsumer? serverView in ServerList.Items) + foreach (var serverView in ServerList.Items) { - serverView?.ProcessFilter(filter); + if(serverView is IFilterConsumer filterConsumer) + filterConsumer.ProcessFilter(filter); } } @@ -83,7 +84,8 @@ public partial class ServerListView : UserControl foreach (var serverEntry in _provider.GetServers()) { ServerList.Items.Add(serverEntry); - serverEntry.ProcessFilter(_currentFilter); + if(serverEntry is IFilterConsumer serverFilter) + serverFilter.ProcessFilter(_currentFilter); } foreach (var error in _provider.GetErrors()) diff --git a/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs index 114f971..468e067 100644 --- a/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs +++ b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs @@ -23,13 +23,13 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS [GenerateProperty] private IServiceProvider ServiceProvider { get; } [GenerateProperty] private ServerViewContainer ServerViewContainer { get; } - private List _serverLists = []; + private List _serverLists = []; private string[] rawServerLists = []; public bool IsLoaded { get; private set; } public Action? OnLoaded { get; set; } public Action? Dirty { get; set; } - public IEnumerable GetServers() + public IEnumerable GetServers() { return _serverLists; } @@ -44,13 +44,13 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS IsLoaded = false; _serverLists.Clear(); var servers = GetFavoriteEntries(); - - _serverLists.AddRange( - servers.Select(s => - ServerViewContainer.Get(s.ToRobustUrl()) - ) + + var serverEntries = servers.Select(s => + ServerViewContainer.Get(s.ToRobustUrl()) ); + _serverLists.AddRange(serverEntries); + _serverLists.Add(new AddFavoriteButton(ServiceProvider)); IsLoaded = true; @@ -67,7 +67,7 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS var servers = GetFavoriteEntries(); servers.Add(robustUrl.ToString()); ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray()); - ServerViewContainer.Get(robustUrl).IsFavorite = true; + if(ServerViewContainer.Get(robustUrl) is IFavoriteEntryModelView favoriteView) favoriteView.IsFavorite = true; } public void RemoveFavorite(ServerEntryModelView entryModelView) @@ -76,6 +76,13 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS servers.Remove(entryModelView.Address.ToString()); ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray()); } + + public void RemoveFavorite(RobustUrl url) + { + var servers = GetFavoriteEntries(); + servers.Remove(url.ToString()); + ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray()); + } private List GetFavoriteEntries() { @@ -103,7 +110,7 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS private void InitialiseInDesignMode(){} } -public class AddFavoriteButton: Border, IFilterConsumer{ +public class AddFavoriteButton: Border, IListEntryModelView{ private Button _addFavoriteButton = new Button(); public AddFavoriteButton(IServiceProvider serviceProvider) @@ -120,8 +127,5 @@ public class AddFavoriteButton: Border, IFilterConsumer{ _addFavoriteButton.Content = "Add Favorite"; Child = _addFavoriteButton; } - - public void ProcessFilter(ServerFilter? serverFilter) - { - } + public bool IsFavorite { get; set; } } \ No newline at end of file diff --git a/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs b/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs index c4ef744..6affa62 100644 --- a/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs +++ b/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs @@ -24,7 +24,7 @@ public sealed partial class HubServerListProvider : IServerListProvider public Action? OnLoaded { get; set; } private CancellationTokenSource? _cts; - private readonly List _servers = []; + private readonly List _servers = []; private readonly List _errors = []; public HubServerListProvider With(string hubUrl) @@ -33,7 +33,7 @@ public sealed partial class HubServerListProvider : IServerListProvider return this; } - public IEnumerable GetServers() + public IEnumerable GetServers() { return _servers; } diff --git a/Nebula.Launcher/ServerListProviders/IServerListProvider.cs b/Nebula.Launcher/ServerListProviders/IServerListProvider.cs index 13d6ccb..d7d2528 100644 --- a/Nebula.Launcher/ServerListProviders/IServerListProvider.cs +++ b/Nebula.Launcher/ServerListProviders/IServerListProvider.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using Nebula.Launcher.ViewModels; +using Nebula.Launcher.ViewModels.Pages; namespace Nebula.Launcher.ServerListProviders; @@ -9,7 +10,7 @@ public interface IServerListProvider public bool IsLoaded { get; } public Action? OnLoaded { get; set; } - public IEnumerable GetServers(); + public IEnumerable GetServers(); public IEnumerable GetErrors(); public void LoadServerList(); diff --git a/Nebula.Launcher/ServerListProviders/TestServerList.cs b/Nebula.Launcher/ServerListProviders/TestServerList.cs index 5bc0e88..dca0fe0 100644 --- a/Nebula.Launcher/ServerListProviders/TestServerList.cs +++ b/Nebula.Launcher/ServerListProviders/TestServerList.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using Nebula.Launcher.Controls; using Nebula.Launcher.ViewModels; +using Nebula.Launcher.ViewModels.Pages; namespace Nebula.Launcher.ServerListProviders; @@ -9,7 +10,7 @@ public sealed class TestServerList : IServerListProvider { public bool IsLoaded => true; public Action? OnLoaded { get; set; } - public IEnumerable GetServers() + public IEnumerable GetServers() { return [new ServerEntryModelView(),new ServerEntryModelView()]; } diff --git a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs index 6beed55..61bdc0a 100644 --- a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs @@ -125,12 +125,6 @@ public static class ConfigControlHelper{ public static object? CreateDefaultValue(Type type) { - if (type == typeof(string)) - return string.Empty; - if (type == typeof(int)) - return 0; - if (type == typeof(float)) - return 0f; if(type.IsValueType) return Activator.CreateInstance(type); diff --git a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs index b165173..2204f6d 100644 --- a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs @@ -29,9 +29,7 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase, IContentHol [ObservableProperty] private string _serverText = ""; [ObservableProperty] private string _searchText = ""; [GenerateProperty] private ContentService ContentService { get; } = default!; - [GenerateProperty] private CancellationService CancellationService { get; } = default!; [GenerateProperty] private FileService FileService { get; } = default!; - [GenerateProperty] private DebugService DebugService { get; } = default!; [GenerateProperty] private PopupMessageService PopupService { get; } = default!; [GenerateProperty] private IServiceProvider ServiceProvider { get; } [GenerateProperty, DesignConstruct] private ViewHelperService ViewHelperService { get; } = default!; diff --git a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs index de040e0..e006757 100644 --- a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Threading; using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.Extensions.DependencyInjection; using Nebula.Launcher.Controls; @@ -89,7 +90,8 @@ public partial class ServerOverviewModel : ViewModelBase { foreach (var entry in ServerViewContainer.Items) { - entry.ProcessFilter(CurrentFilter); + if(entry is IFilterConsumer filterConsumer) + filterConsumer.ProcessFilter(CurrentFilter); } } @@ -109,6 +111,7 @@ public partial class ServerOverviewModel : ViewModelBase public void UpdateRequired() { + ServerViewContainer.Clear(); CurrentServerList.RefreshFromProvider(); CurrentServerList.RequireStatusUpdate(); CurrentServerList.ApplyFilter(CurrentFilter); @@ -150,25 +153,25 @@ public class ServerViewContainer foreach (var favorite in favorites) { - if (_entries.TryGetValue(favorite, out var entry)) + if (_entries.TryGetValue(favorite, out var entry) && entry is IFavoriteEntryModelView favoriteView) { - entry.IsFavorite = true; + favoriteView.IsFavorite = true; } } } - private readonly Dictionary _entries = new(); + private readonly Dictionary _entries = new(); - public ICollection Items => _entries.Values; + public ICollection Items => _entries.Values; public void Clear() { _entries.Clear(); } - public ServerEntryModelView Get(RobustUrl url, ServerStatus? serverStatus = null) + public IListEntryModelView Get(RobustUrl url, ServerStatus? serverStatus = null) { - ServerEntryModelView? entry; + IListEntryModelView? entry; lock (_entries) { @@ -177,9 +180,13 @@ public class ServerViewContainer return entry; } - entry = _viewHelperService.GetViewModel().WithData(url, serverStatus); + if (serverStatus is not null) + entry = _viewHelperService.GetViewModel().WithData(url, serverStatus); + else + entry = _viewHelperService.GetViewModel().LoadServerEntry(url, CancellationToken.None); - if(favorites.Contains(url.ToString())) entry.IsFavorite = true; + if(favorites.Contains(url.ToString()) && entry is IFavoriteEntryModelView favoriteEntryModelView) + favoriteEntryModelView.IsFavorite = true; _entries.Add(url.ToString(), entry); } @@ -188,6 +195,16 @@ public class ServerViewContainer } } +public interface IListEntryModelView +{ + +} + +public interface IFavoriteEntryModelView +{ + public bool IsFavorite { get; set; } +} + public class ServerComparer : IComparer, IComparer, IComparer<(RobustUrl,ServerStatus)> { public int Compare(ServerHubInfo? x, ServerHubInfo? y) diff --git a/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs new file mode 100644 index 0000000..eb47a6f --- /dev/null +++ b/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs @@ -0,0 +1,89 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using Microsoft.Extensions.DependencyInjection; +using Nebula.Launcher.ServerListProviders; +using Nebula.Launcher.ViewModels.Pages; +using Nebula.Launcher.Views; +using Nebula.Shared.Models; +using Nebula.Shared.Services; +using BindingFlags = System.Reflection.BindingFlags; + +namespace Nebula.Launcher.ViewModels; + +[ViewModelRegister(typeof(ServerCompoundEntryView), false)] +[ConstructGenerator] +public sealed partial class ServerCompoundEntryViewModel : + ViewModelBase, IFavoriteEntryModelView, IFilterConsumer, IListEntryModelView +{ + [ObservableProperty] private ServerEntryModelView _currentEntry; + [ObservableProperty] private Control? _entryControl; + [ObservableProperty] private string _name = "Loading..."; + [ObservableProperty] private bool _isFavorite; + [ObservableProperty] private bool _loading = true; + + [GenerateProperty] private RestService RestService { get; } + [GenerateProperty] private IServiceProvider ServiceProvider{ get; } + [GenerateProperty] private FavoriteServerListProvider FavoriteServerListProvider { get; } + + private RobustUrl? _url; + + + protected override void InitialiseInDesignMode() + { + } + + protected override void Initialise() + { + } + + public ServerCompoundEntryViewModel LoadServerEntry(RobustUrl url, CancellationToken cancellationToken) + { + Task.Run(async () => + { + try + { + _url = url; + Name = $"Loading {url}..."; + var status = await RestService.GetAsync(url.StatusUri, cancellationToken); + + await Dispatcher.UIThread.InvokeAsync(() => + { + CurrentEntry = ServiceProvider.GetService()!.WithData(url, status); + CurrentEntry.IsFavorite = IsFavorite; + CurrentEntry.Loading = false; + Loading = false; + }); + } + catch (Exception e) + { + var error = new Exception("Unable to load server entry", e); + Name = e.Message; + } + }, cancellationToken); + + return this; + } + + public void ToggleFavorites() + { + if (_url == null) + return; + IsFavorite = !IsFavorite; + if(IsFavorite) + FavoriteServerListProvider.AddFavorite(_url); + else + FavoriteServerListProvider.RemoveFavorite(_url); + } + + + public void ProcessFilter(ServerFilter? serverFilter) + { + if(CurrentEntry is IFilterConsumer filterConsumer) + filterConsumer.ProcessFilter(serverFilter); + } +} \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs index b9301d6..31327d9 100644 --- a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs +++ b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs @@ -21,21 +21,20 @@ namespace Nebula.Launcher.ViewModels; [ViewModelRegister(typeof(ServerEntryView), false)] [ConstructGenerator] -public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer +public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, IListEntryModelView, IFavoriteEntryModelView { [ObservableProperty] private string _description = "Fetching info..."; [ObservableProperty] private bool _expandInfo; [ObservableProperty] private bool _isFavorite; [ObservableProperty] private bool _isVisible; [ObservableProperty] private bool _runVisible = true; + [ObservableProperty] private bool _tagDataVisible; + [ObservableProperty] private bool _loading; private ILogger _logger; - private bool _isStatusFromHub; private ServerInfo? _serverInfo; private ContentLogConsumer _currentContentLogConsumer; private ProcessRunHandler? _currentInstance; - - [ObservableProperty] private bool _tagDataVisible; public LogPopupModelView CurrLog; public RobustUrl Address { get; private set; } @@ -119,39 +118,14 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer foreach (var tag in Status.Tags) Tags.Add(tag); OnPropertyChanged(nameof(Status)); } + - public void UpdateStatusIfNecessary() - { - if(_isStatusFromHub) return; - FetchStatus(); - } - - public ServerEntryModelView WithData(RobustUrl url, ServerStatus? serverStatus) + public ServerEntryModelView WithData(RobustUrl url, ServerStatus serverStatus) { Address = url; - _isStatusFromHub = serverStatus is not null; - if (_isStatusFromHub) - SetStatus(serverStatus!); - else - FetchStatus(); - + SetStatus(serverStatus); return this; } - - private async void FetchStatus() - { - try - { - SetStatus(await RestService.GetAsync(Address.StatusUri, CancellationService.Token)); - } - catch (Exception e) - { - _logger.Error(e); - Status = new ServerStatus("ErrorLand", $"ERROR: {e.Message}", [], "", -1, -1, -1, false, - DateTime.Now, - -1); - } - } public void OpenContentViewer() { diff --git a/Nebula.Launcher/Views/ServerCompoundEntryView.axaml b/Nebula.Launcher/Views/ServerCompoundEntryView.axaml new file mode 100644 index 0000000..95f9220 --- /dev/null +++ b/Nebula.Launcher/Views/ServerCompoundEntryView.axaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Nebula.Launcher/Views/ServerCompoundEntryView.axaml.cs b/Nebula.Launcher/Views/ServerCompoundEntryView.axaml.cs new file mode 100644 index 0000000..73376fe --- /dev/null +++ b/Nebula.Launcher/Views/ServerCompoundEntryView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Controls; +using Nebula.Launcher.ViewModels; + +namespace Nebula.Launcher.Views; + +public partial class ServerCompoundEntryView : UserControl +{ + public ServerCompoundEntryView() + { + InitializeComponent(); + } + + public ServerCompoundEntryView(ServerCompoundEntryViewModel viewModel) : this() + { + DataContext = viewModel; + } +} \ No newline at end of file diff --git a/Nebula.Shared/Services/RestService.cs b/Nebula.Shared/Services/RestService.cs index 5cc2c25..1cf0cb4 100644 --- a/Nebula.Shared/Services/RestService.cs +++ b/Nebula.Shared/Services/RestService.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.Contracts; using System.Globalization; using System.Net; using System.Text; @@ -24,12 +25,14 @@ public class RestService _logger = debug.GetLogger(this); } + [Pure] public async Task GetAsync(Uri uri, CancellationToken cancellationToken) where T : notnull { var response = await _client.GetAsync(uri, cancellationToken); return await ReadResult(response, cancellationToken, uri); } + [Pure] public async Task GetAsyncDefault(Uri uri, T defaultValue, CancellationToken cancellationToken) where T : notnull { try @@ -43,6 +46,7 @@ public class RestService } } + [Pure] public async Task PostAsync(T information, Uri uri, CancellationToken cancellationToken) where K : notnull { var json = JsonSerializer.Serialize(information, _serializerOptions); @@ -51,6 +55,7 @@ public class RestService return await ReadResult(response, cancellationToken, uri); } + [Pure] public async Task PostAsync(Stream stream, Uri uri, CancellationToken cancellationToken) where T : notnull { using var multipartFormContent = @@ -60,12 +65,14 @@ public class RestService return await ReadResult(response, cancellationToken, uri); } + [Pure] public async Task DeleteAsync(Uri uri, CancellationToken cancellationToken) where T : notnull { var response = await _client.DeleteAsync(uri, cancellationToken); return await ReadResult(response, cancellationToken, uri); } + [Pure] private async Task ReadResult(HttpResponseMessage response, CancellationToken cancellationToken, Uri uri) where T : notnull { var content = await response.Content.ReadAsStringAsync(cancellationToken); diff --git a/Nebula.sln.DotSettings.user b/Nebula.sln.DotSettings.user index c0071bb..d5419b8 100644 --- a/Nebula.sln.DotSettings.user +++ b/Nebula.sln.DotSettings.user @@ -9,6 +9,8 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -18,6 +20,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded