diff --git a/.idea/.idea.Nebula/.idea/avalonia.xml b/.idea/.idea.Nebula/.idea/avalonia.xml index 50bc6a6..5a43723 100644 --- a/.idea/.idea.Nebula/.idea/avalonia.xml +++ b/.idea/.idea.Nebula/.idea/avalonia.xml @@ -28,6 +28,8 @@ + + diff --git a/Nebula.Launcher/Assets/svg/pencil.svg b/Nebula.Launcher/Assets/svg/pencil.svg new file mode 100644 index 0000000..b59ab45 --- /dev/null +++ b/Nebula.Launcher/Assets/svg/pencil.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Nebula.Launcher/LauncherConVar.cs b/Nebula.Launcher/LauncherConVar.cs index d4b1ae7..a14ac80 100644 --- a/Nebula.Launcher/LauncherConVar.cs +++ b/Nebula.Launcher/LauncherConVar.cs @@ -18,7 +18,11 @@ public static class LauncherConVar public static readonly ConVar Favorites = ConVarBuilder.Build("server.favorites", []); - public static readonly ConVar AuthServers = ConVarBuilder.Build("launcher.authServers", [ + public static readonly ConVar> ServerCustomNames = + ConVarBuilder.Build>("server.names", []); + + public static readonly ConVar AuthServers = + ConVarBuilder.Build("launcher.authServers", [ new AuthServerCredentials( "WizDen", [ diff --git a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs index e006757..75de996 100644 --- a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Threading; using CommunityToolkit.Mvvm.ComponentModel; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Nebula.Launcher.Controls; using Nebula.Launcher.Models; @@ -24,24 +25,18 @@ public partial class ServerOverviewModel : ViewModelBase [ObservableProperty] private bool _isFilterVisible; - [ObservableProperty] private ServerListView _currentServerList = new ServerListView(); + [ObservableProperty] private ServerListView _currentServerList = new(); - public readonly ServerFilter CurrentFilter = new ServerFilter(); + public readonly ServerFilter CurrentFilter = new(); public Action? OnSearchChange; - - [GenerateProperty] private PopupMessageService PopupMessageService { get; } - [GenerateProperty] private CancellationService CancellationService { get; } - [GenerateProperty] private DebugService DebugService { get; } [GenerateProperty] private IServiceProvider ServiceProvider { get; } [GenerateProperty] private ConfigurationService ConfigurationService { get; } [GenerateProperty] private FavoriteServerListProvider FavoriteServerListProvider { get; } - [GenerateProperty, DesignConstruct] private ViewHelperService ViewHelperService { get; } - public ObservableCollection Items { get; private set; } [ObservableProperty] private ServerListTabTemplate _selectedItem; - [GenerateProperty, DesignConstruct] private ServerViewContainer ServerViewContainer { get; set; } + [GenerateProperty, DesignConstruct] private ServerViewContainer ServerViewContainer { get; } private Dictionary _viewCache = []; @@ -126,6 +121,7 @@ public partial class ServerOverviewModel : ViewModelBase } CurrentServerList = view; + ApplyFilter(); } } @@ -134,25 +130,77 @@ public partial class ServerOverviewModel : ViewModelBase public class ServerViewContainer { private readonly ViewHelperService _viewHelperService; - private List favorites = []; + private readonly List _favorites = []; + private readonly Dictionary _customNames = []; public ServerViewContainer() { _viewHelperService = new ViewHelperService(); } + [UsedImplicitly] public ServerViewContainer(ViewHelperService viewHelperService, ConfigurationService configurationService) { _viewHelperService = viewHelperService; configurationService.SubscribeVarChanged(LauncherConVar.Favorites, OnFavoritesChange, true); + configurationService.SubscribeVarChanged(LauncherConVar.ServerCustomNames, OnCustomNamesChanged, true); + } + + private void OnCustomNamesChanged(Dictionary? value) + { + var oldNames = + _customNames.ToDictionary(k => k.Key, v => v.Value); //Clone think + + _customNames.Clear(); + + if(value == null) + { + foreach (var (ip,_) in oldNames) + { + if(!_entries.TryGetValue(ip, out var listEntry) || listEntry is not IEntryNameHolder entryNameHolder) + continue; + + entryNameHolder.Name = null; + } + + return; + } + + foreach (var (oldIp, oldName) in oldNames) + { + if(value.TryGetValue(oldIp, out var newName)) + { + if (oldName == newName) + value.Remove(newName); + + continue; + } + + if(!_entries.TryGetValue(oldIp, out var listEntry) || + listEntry is not IEntryNameHolder entryNameHolder) + continue; + + entryNameHolder.Name = null; + } + + foreach (var (ip, name) in value) + { + _customNames.Add(ip, name); + if(!_entries.TryGetValue(ip, out var listEntry) || listEntry is not IEntryNameHolder entryNameHolder) + continue; + + entryNameHolder.Name = name; + } } private void OnFavoritesChange(string[]? value) { - favorites = new List(value ?? []); + _favorites.Clear(); + if(value == null) return; - foreach (var favorite in favorites) + foreach (var favorite in value) { + _favorites.Add(favorite); if (_entries.TryGetValue(favorite, out var entry) && entry is IFavoriteEntryModelView favoriteView) { favoriteView.IsFavorite = true; @@ -175,17 +223,20 @@ public class ServerViewContainer lock (_entries) { + _customNames.TryGetValue(url.ToString(), out var customName); + if (_entries.TryGetValue(url.ToString(), out entry)) { return entry; } if (serverStatus is not null) - entry = _viewHelperService.GetViewModel().WithData(url, serverStatus); + entry = _viewHelperService.GetViewModel().WithData(url, customName, serverStatus); else - entry = _viewHelperService.GetViewModel().LoadServerEntry(url, CancellationToken.None); + entry = _viewHelperService.GetViewModel().LoadServerEntry(url, customName, CancellationToken.None); - if(favorites.Contains(url.ToString()) && entry is IFavoriteEntryModelView favoriteEntryModelView) + if(_favorites.Contains(url.ToString()) && + entry is IFavoriteEntryModelView favoriteEntryModelView) favoriteEntryModelView.IsFavorite = true; _entries.Add(url.ToString(), entry); @@ -205,6 +256,11 @@ public interface IFavoriteEntryModelView public bool IsFavorite { get; set; } } +public interface IEntryNameHolder +{ + public string? Name { get; set; } +} + public class ServerComparer : IComparer, IComparer, IComparer<(RobustUrl,ServerStatus)> { public int Compare(ServerHubInfo? x, ServerHubInfo? y) @@ -262,4 +318,6 @@ public sealed class ServerFilter { return IsMatchByName(name) && IsMatchByTags(itemTags); } -} \ No newline at end of file +} + +public sealed record ServerCustomNameEntry(string Url, string Name); \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/Popup/EditServerNameViewModel.cs b/Nebula.Launcher/ViewModels/Popup/EditServerNameViewModel.cs new file mode 100644 index 0000000..6a54204 --- /dev/null +++ b/Nebula.Launcher/ViewModels/Popup/EditServerNameViewModel.cs @@ -0,0 +1,56 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using Nebula.Launcher.Views.Popup; +using Nebula.Shared.Services; + +namespace Nebula.Launcher.ViewModels.Popup; + +[ViewModelRegister(typeof(EditServerNameView), false)] +[ConstructGenerator] +public sealed partial class EditServerNameViewModel : PopupViewModelBase +{ + [GenerateProperty] public override PopupMessageService PopupMessageService { get; } + [GenerateProperty] public ConfigurationService ConfigurationService { get; } + public override string Title => "Edit server name"; + public override bool IsClosable => true; + + [ObservableProperty] private string _ipInput; + [ObservableProperty] private string _nameInput; + + public void OnEnter() + { + if(string.IsNullOrWhiteSpace(IpInput)) + return; + + if (string.IsNullOrWhiteSpace(NameInput)) + { + RemoveServerName(); + Dispose(); + return; + } + + AddServerName(); + Dispose(); + } + + private void AddServerName() + { + var currentNames = ConfigurationService.GetConfigValue(LauncherConVar.ServerCustomNames)!; + currentNames.Add(IpInput, NameInput); + ConfigurationService.SetConfigValue(LauncherConVar.ServerCustomNames, currentNames); + } + + private void RemoveServerName() + { + var currentNames = ConfigurationService.GetConfigValue(LauncherConVar.ServerCustomNames)!; + currentNames.Remove(IpInput); + ConfigurationService.SetConfigValue(LauncherConVar.ServerCustomNames, currentNames); + } + + protected override void InitialiseInDesignMode() + { + } + + protected override void Initialise() + { + } +} \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs index eb47a6f..c15c938 100644 --- a/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs +++ b/Nebula.Launcher/ViewModels/ServerCompoundEntryModelView.cs @@ -18,51 +18,59 @@ namespace Nebula.Launcher.ViewModels; [ViewModelRegister(typeof(ServerCompoundEntryView), false)] [ConstructGenerator] public sealed partial class ServerCompoundEntryViewModel : - ViewModelBase, IFavoriteEntryModelView, IFilterConsumer, IListEntryModelView + ViewModelBase, IFavoriteEntryModelView, IFilterConsumer, IListEntryModelView, IEntryNameHolder { - [ObservableProperty] private ServerEntryModelView _currentEntry; + [ObservableProperty] private ServerEntryModelView? _currentEntry; [ObservableProperty] private Control? _entryControl; - [ObservableProperty] private string _name = "Loading..."; + [ObservableProperty] private string _message = "Loading server entry..."; [ObservableProperty] private bool _isFavorite; [ObservableProperty] private bool _loading = true; + private string? _name; + + public string? Name + { + get => _name; + set + { + _name = value; + OnPropertyChanged(); + + if (CurrentEntry != null) + CurrentEntry.Name = value; + } + } + [GenerateProperty] private RestService RestService { get; } [GenerateProperty] private IServiceProvider ServiceProvider{ get; } [GenerateProperty] private FavoriteServerListProvider FavoriteServerListProvider { get; } - private RobustUrl? _url; - - protected override void InitialiseInDesignMode() { + Name = "TEST.TEST"; } protected override void Initialise() { } - public ServerCompoundEntryViewModel LoadServerEntry(RobustUrl url, CancellationToken cancellationToken) + public ServerCompoundEntryViewModel LoadServerEntry(RobustUrl url,string? name, CancellationToken cancellationToken) { Task.Run(async () => { try { - _url = url; - Name = $"Loading {url}..."; + Message = "Loading server entry..."; 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; - }); + CurrentEntry = ServiceProvider.GetService()!.WithData(url,name, 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; + Message = e.Message; } }, cancellationToken); @@ -71,13 +79,7 @@ public sealed partial class ServerCompoundEntryViewModel : public void ToggleFavorites() { - if (_url == null) - return; - IsFavorite = !IsFavorite; - if(IsFavorite) - FavoriteServerListProvider.AddFavorite(_url); - else - FavoriteServerListProvider.RemoveFavorite(_url); + CurrentEntry?.ToggleFavorites(); } diff --git a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs index 31327d9..2d59854 100644 --- a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs +++ b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs @@ -21,7 +21,7 @@ namespace Nebula.Launcher.ViewModels; [ViewModelRegister(typeof(ServerEntryView), false)] [ConstructGenerator] -public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, IListEntryModelView, IFavoriteEntryModelView +public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, IListEntryModelView, IFavoriteEntryModelView, IEntryNameHolder { [ObservableProperty] private string _description = "Fetching info..."; [ObservableProperty] private bool _expandInfo; @@ -30,6 +30,13 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis [ObservableProperty] private bool _runVisible = true; [ObservableProperty] private bool _tagDataVisible; [ObservableProperty] private bool _loading; + [ObservableProperty] private string _realName; + + public string? Name + { + get => RealName; + set => RealName = value ?? Status.Name; + } private ILogger _logger; private ServerInfo? _serverInfo; @@ -83,6 +90,8 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis protected override void InitialiseInDesignMode() { + IsVisible = true; + RealName = "TEST.TEST"; Description = "Server of meow girls! Nya~ \nNyaMeow\nOOOINK!!"; Links.Add(new ServerLink("Discord", "discord", "https://cinka.ru")); Status = new ServerStatus("Ameba", @@ -119,14 +128,22 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer, ILis OnPropertyChanged(nameof(Status)); } - - public ServerEntryModelView WithData(RobustUrl url, ServerStatus serverStatus) + public ServerEntryModelView WithData(RobustUrl url, string? name,ServerStatus serverStatus) { Address = url; SetStatus(serverStatus); + Name = name; return this; } + public void EditName() + { + var popup = ViewHelperService.GetViewModel(); + popup.IpInput = Address.ToString(); + popup.NameInput = Name ?? string.Empty; + PopupMessageService.Popup(popup); + } + public void OpenContentViewer() { MainViewModel.RequirePage().Go(Address, ContentPath.Empty); diff --git a/Nebula.Launcher/Views/Popup/EditServerNameView.axaml b/Nebula.Launcher/Views/Popup/EditServerNameView.axaml new file mode 100644 index 0000000..36d5cda --- /dev/null +++ b/Nebula.Launcher/Views/Popup/EditServerNameView.axaml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + diff --git a/Nebula.Launcher/Views/Popup/EditServerNameView.axaml.cs b/Nebula.Launcher/Views/Popup/EditServerNameView.axaml.cs new file mode 100644 index 0000000..a95b06a --- /dev/null +++ b/Nebula.Launcher/Views/Popup/EditServerNameView.axaml.cs @@ -0,0 +1,20 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using Nebula.Launcher.ViewModels.Popup; + +namespace Nebula.Launcher.Views.Popup; + +public partial class EditServerNameView : UserControl +{ + public EditServerNameView() + { + InitializeComponent(); + } + + public EditServerNameView(EditServerNameViewModel viewModel) + : this() + { + DataContext = viewModel; + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Views/ServerCompoundEntryView.axaml b/Nebula.Launcher/Views/ServerCompoundEntryView.axaml index 95f9220..a9b1122 100644 --- a/Nebula.Launcher/Views/ServerCompoundEntryView.axaml +++ b/Nebula.Launcher/Views/ServerCompoundEntryView.axaml @@ -36,9 +36,14 @@ Margin="10,0,0,0" VerticalScrollBarVisibility="Disabled" x:Name="AutoScrollViewer"> - + + + + - + @@ -67,9 +67,19 @@ - + + + +