diff --git a/.idea/.idea.Nebula/.idea/avalonia.xml b/.idea/.idea.Nebula/.idea/avalonia.xml
index 0f301fd..1245061 100644
--- a/.idea/.idea.Nebula/.idea/avalonia.xml
+++ b/.idea/.idea.Nebula/.idea/avalonia.xml
@@ -8,6 +8,7 @@
+
@@ -23,6 +24,7 @@
+
@@ -33,6 +35,7 @@
+
diff --git a/Nebula.Launcher/App.axaml.cs b/Nebula.Launcher/App.axaml.cs
index 55d7c70..9628f97 100644
--- a/Nebula.Launcher/App.axaml.cs
+++ b/Nebula.Launcher/App.axaml.cs
@@ -1,5 +1,3 @@
-using System;
-using System.Globalization;
using System.Linq;
using Avalonia;
using Avalonia.Controls;
@@ -8,7 +6,6 @@ using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml;
using Microsoft.Extensions.DependencyInjection;
using Nebula.Launcher.MessageBox;
-using Nebula.Launcher.ViewModels.ContentView;
using Nebula.Launcher.Views;
using Nebula.Shared;
using Nebula.Shared.Services;
@@ -63,7 +60,6 @@ public class App : Application
services.AddAvaloniaServices();
services.AddServices();
services.AddViews();
- services.AddTransient();
var serviceProvider = services.BuildServiceProvider();
diff --git a/Nebula.Launcher/Controls/ServerListView.axaml b/Nebula.Launcher/Controls/ServerListView.axaml
new file mode 100644
index 0000000..ec2de2c
--- /dev/null
+++ b/Nebula.Launcher/Controls/ServerListView.axaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
diff --git a/Nebula.Launcher/Controls/ServerListView.axaml.cs b/Nebula.Launcher/Controls/ServerListView.axaml.cs
new file mode 100644
index 0000000..fd11a68
--- /dev/null
+++ b/Nebula.Launcher/Controls/ServerListView.axaml.cs
@@ -0,0 +1,104 @@
+using Avalonia.Controls;
+using Nebula.Launcher.ServerListProviders;
+using Nebula.Launcher.ViewModels;
+using Nebula.Launcher.ViewModels.Pages;
+
+namespace Nebula.Launcher.Controls;
+
+public partial class ServerListView : UserControl
+{
+ private IServerListProvider _provider = default!;
+ private ServerFilter? _currentFilter;
+
+ public bool IsLoading { get; private set; }
+
+ public ServerListView()
+ {
+ InitializeComponent();
+ }
+
+ public static ServerListView TakeFrom(IServerListProvider provider)
+ {
+ var serverListView = new ServerListView();
+ if (provider is IServerListDirtyInvoker invoker)
+ {
+ invoker.Dirty += serverListView.OnDirty;
+ }
+ serverListView._provider = provider;
+ serverListView.RefreshFromProvider();
+ return serverListView;
+ }
+
+ public void RefreshFromProvider()
+ {
+ if (IsLoading)
+ return;
+
+ Clear();
+ StartLoading();
+
+ _provider.LoadServerList();
+
+ if (_provider.IsLoaded) PasteServersFromList();
+ else _provider.OnLoaded += RefreshRequired;
+ }
+
+ public void ApplyFilter(ServerFilter? filter)
+ {
+ _currentFilter = filter;
+
+ if(IsLoading)
+ return;
+
+ foreach (IFilterConsumer? serverView in ServerList.Items)
+ {
+ serverView?.ProcessFilter(filter);
+ }
+ }
+
+ private void OnDirty()
+ {
+ RefreshFromProvider();
+ }
+
+ private void Clear()
+ {
+ ErrorList.Items.Clear();
+ ServerList.Items.Clear();
+ }
+
+ private void PasteServersFromList()
+ {
+ foreach (var serverEntry in _provider.GetServers())
+ {
+ ServerList.Items.Add(serverEntry);
+ serverEntry.ProcessFilter(_currentFilter);
+ }
+
+ foreach (var error in _provider.GetErrors())
+ {
+ ErrorList.Items.Add(error);
+ }
+
+ EndLoading();
+ }
+
+ private void RefreshRequired()
+ {
+ PasteServersFromList();
+ _provider.OnLoaded -= RefreshRequired;
+ }
+
+ private void StartLoading()
+ {
+ Clear();
+ IsLoading = true;
+ LoadingLabel.IsVisible = true;
+ }
+
+ private void EndLoading()
+ {
+ IsLoading = false;
+ LoadingLabel.IsVisible = false;
+ }
+}
\ No newline at end of file
diff --git a/Nebula.Launcher/LauncherConVar.cs b/Nebula.Launcher/LauncherConVar.cs
index f10a8e4..756ad56 100644
--- a/Nebula.Launcher/LauncherConVar.cs
+++ b/Nebula.Launcher/LauncherConVar.cs
@@ -1,3 +1,4 @@
+using Nebula.Launcher.Models;
using Nebula.Launcher.ViewModels.Pages;
using Nebula.Shared.Services;
@@ -22,6 +23,11 @@ public static class LauncherConVar
"https://auth.fallback.spacestation14.com/"
])
]);
+
+ public static readonly ConVar Hub = ConVarBuilder.Build("launcher.hub.v2", [
+ new ServerHubRecord("WizDen", "https://hub.spacestation14.com/api/servers", null),
+ new ServerHubRecord("AltHub","https://web.networkgamez.com/api/servers",null)
+ ]);
public static readonly ConVar CurrentLang = ConVarBuilder.Build("launcher.language", "en-US");
public static readonly ConVar ILSpyUrl = ConVarBuilder.Build("decompiler.url",
diff --git a/Nebula.Launcher/Models/ListItemTemplate.cs b/Nebula.Launcher/Models/ListItemTemplate.cs
new file mode 100644
index 0000000..71646da
--- /dev/null
+++ b/Nebula.Launcher/Models/ListItemTemplate.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Text.Json.Serialization;
+using Nebula.Launcher.ServerListProviders;
+
+namespace Nebula.Launcher.Models;
+
+public record ListItemTemplate(Type ModelType, string IconKey, string Label);
+public record ServerListTabTemplate(IServerListProvider ServerListProvider, string TabName);
+public record ServerHubRecord(
+ [property:JsonPropertyName("name")] string Name,
+ [property:JsonPropertyName("url")] string MainUrl,
+ [property:JsonPropertyName("fallback")] string? Fallback);
\ No newline at end of file
diff --git a/Nebula.Launcher/Nebula.Launcher.csproj b/Nebula.Launcher/Nebula.Launcher.csproj
index 9ab546c..b677a12 100644
--- a/Nebula.Launcher/Nebula.Launcher.csproj
+++ b/Nebula.Launcher/Nebula.Launcher.csproj
@@ -42,6 +42,10 @@
AddFavoriteView.axaml
Code
+
+ ServerListView.axaml
+ Code
+
@@ -69,4 +73,8 @@
+
+
+
+
diff --git a/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs
new file mode 100644
index 0000000..8146b69
--- /dev/null
+++ b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.DependencyInjection;
+using Nebula.Launcher.ViewModels;
+using Nebula.Launcher.ViewModels.Pages;
+using Nebula.Shared;
+using Nebula.Shared.Models;
+using Nebula.Shared.Services;
+using Nebula.Shared.Utils;
+
+namespace Nebula.Launcher.ServerListProviders;
+
+[ServiceRegister(), ConstructGenerator]
+public sealed partial class FavoriteServerListProvider : IServerListProvider, IServerListDirtyInvoker
+{
+ [GenerateProperty] private ConfigurationService ConfigurationService { get; }
+ [GenerateProperty] private RestService RestService { get; }
+ [GenerateProperty] private ServerViewContainer ServerViewContainer { get; }
+
+ private List _serverLists = [];
+
+ public bool IsLoaded { get; private set; }
+ public Action? OnLoaded { get; set; }
+ public Action? Dirty { get; set; }
+ public IEnumerable GetServers()
+ {
+ return _serverLists;
+ }
+
+ public IEnumerable GetErrors()
+ {
+ return [];
+ }
+
+ public void LoadServerList()
+ {
+ IsLoaded = false;
+ _serverLists.Clear();
+ var servers = GetFavoriteEntries();
+
+ _serverLists.AddRange(
+ servers.Select(s =>
+ ServerViewContainer.Get(s.ToRobustUrl())
+ )
+ );
+ IsLoaded = true;
+ OnLoaded?.Invoke();
+ }
+
+ public void AddFavorite(ServerEntryModelView entryModelView)
+ {
+ entryModelView.IsFavorite = true;
+ AddFavorite(entryModelView.Address);
+ }
+
+ public void AddFavorite(RobustUrl robustUrl)
+ {
+ var servers = GetFavoriteEntries();
+ servers.Add(robustUrl.ToString());
+ ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray());
+ Dirty?.Invoke();
+ }
+
+ public void RemoveFavorite(ServerEntryModelView entryModelView)
+ {
+ var servers = GetFavoriteEntries();
+ servers.Remove(entryModelView.Address.ToString());
+ ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray());
+ Dirty?.Invoke();
+ }
+
+ private List GetFavoriteEntries()
+ {
+ return ConfigurationService.GetConfigValue(LauncherConVar.Favorites)?.ToList() ?? [];
+ }
+
+ private void Initialise(){}
+ private void InitialiseInDesignMode(){}
+}
\ No newline at end of file
diff --git a/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs b/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs
new file mode 100644
index 0000000..c4ef744
--- /dev/null
+++ b/Nebula.Launcher/ServerListProviders/HubServerListProvider.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using Microsoft.Extensions.DependencyInjection;
+using Nebula.Launcher.ViewModels;
+using Nebula.Launcher.ViewModels.Pages;
+using Nebula.Shared;
+using Nebula.Shared.Models;
+using Nebula.Shared.Services;
+using Nebula.Shared.Utils;
+
+namespace Nebula.Launcher.ServerListProviders;
+
+[ServiceRegister(null, false), ConstructGenerator]
+public sealed partial class HubServerListProvider : IServerListProvider
+{
+ [GenerateProperty] private RestService RestService { get; }
+ [GenerateProperty] private ServerViewContainer ServerViewContainer { get; }
+
+ public string HubUrl { get; set; }
+
+ public bool IsLoaded { get; private set; }
+ public Action? OnLoaded { get; set; }
+
+ private CancellationTokenSource? _cts;
+ private readonly List _servers = [];
+ private readonly List _errors = [];
+
+ public HubServerListProvider With(string hubUrl)
+ {
+ HubUrl = hubUrl;
+ return this;
+ }
+
+ public IEnumerable GetServers()
+ {
+ return _servers;
+ }
+
+ public IEnumerable GetErrors()
+ {
+ return _errors;
+ }
+
+ public async void LoadServerList()
+ {
+ if (_cts != null)
+ {
+ await _cts.CancelAsync();
+ _cts = null;
+ }
+
+ _servers.Clear();
+ _errors.Clear();
+ IsLoaded = false;
+ _cts = new CancellationTokenSource();
+
+ try
+ {
+ var servers =
+ await RestService.GetAsync>(new Uri(HubUrl), _cts.Token);
+
+ servers.Sort(new ServerComparer());
+
+ if(_cts.Token.IsCancellationRequested) return;
+
+ _servers.AddRange(
+ servers.Select(h=>
+ ServerViewContainer.Get(h.Address.ToRobustUrl(), h.StatusData)
+ )
+ );
+ }
+ catch (Exception e)
+ {
+ _errors.Add(new Exception($"Some error while loading server list from {HubUrl}. See inner exception", e));
+ }
+
+ IsLoaded = true;
+ OnLoaded?.Invoke();
+ }
+
+ private void Initialise(){}
+ private void InitialiseInDesignMode(){}
+}
\ No newline at end of file
diff --git a/Nebula.Launcher/ServerListProviders/IServerListProvider.cs b/Nebula.Launcher/ServerListProviders/IServerListProvider.cs
new file mode 100644
index 0000000..13d6ccb
--- /dev/null
+++ b/Nebula.Launcher/ServerListProviders/IServerListProvider.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using Nebula.Launcher.ViewModels;
+
+namespace Nebula.Launcher.ServerListProviders;
+
+public interface IServerListProvider
+{
+ public bool IsLoaded { get; }
+ public Action? OnLoaded { get; set; }
+
+ public IEnumerable GetServers();
+ public IEnumerable GetErrors();
+
+ public void LoadServerList();
+}
+
+public interface IServerListDirtyInvoker
+{
+ public Action? Dirty { get; set; }
+}
\ No newline at end of file
diff --git a/Nebula.Launcher/ServerListProviders/TestServerList.cs b/Nebula.Launcher/ServerListProviders/TestServerList.cs
new file mode 100644
index 0000000..5bc0e88
--- /dev/null
+++ b/Nebula.Launcher/ServerListProviders/TestServerList.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using Nebula.Launcher.Controls;
+using Nebula.Launcher.ViewModels;
+
+namespace Nebula.Launcher.ServerListProviders;
+
+public sealed class TestServerList : IServerListProvider
+{
+ public bool IsLoaded => true;
+ public Action? OnLoaded { get; set; }
+ public IEnumerable GetServers()
+ {
+ return [new ServerEntryModelView(),new ServerEntryModelView()];
+ }
+
+ public IEnumerable GetErrors()
+ {
+ return [new Exception("On no!")];
+ }
+
+ public void LoadServerList()
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/Nebula.Launcher/ViewModels/ContentView/ContentViewBase.cs b/Nebula.Launcher/ViewModels/ContentView/ContentViewBase.cs
deleted file mode 100644
index f6f24b1..0000000
--- a/Nebula.Launcher/ViewModels/ContentView/ContentViewBase.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System;
-using System.IO;
-using Nebula.Launcher.ViewModels.Pages;
-
-namespace Nebula.Launcher.ViewModels.ContentView;
-public abstract class ContentViewBase : ViewModelBase, IDisposable
-{
- public virtual void InitialiseWithData(ContentPath path, Stream stream, ContentEntry contentEntry)
- {
- }
- public virtual void Dispose()
- {
- }
-}
\ No newline at end of file
diff --git a/Nebula.Launcher/ViewModels/ContentView/DecompilerContentView.cs b/Nebula.Launcher/ViewModels/ContentView/DecompilerContentView.cs
deleted file mode 100644
index e5471de..0000000
--- a/Nebula.Launcher/ViewModels/ContentView/DecompilerContentView.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System.IO;
-using Nebula.Launcher.Services;
-using Nebula.Launcher.ViewModels.Pages;
-using Nebula.Shared.Utils;
-
-namespace Nebula.Launcher.ViewModels.ContentView;
-
-[ConstructGenerator]
-public sealed partial class DecompilerContentView: ContentViewBase
-{
- [GenerateProperty] private DecompilerService decompilerService {get;}
-
- public override void InitialiseWithData(ContentPath path, Stream stream, ContentEntry contentEntry)
- {
- base.InitialiseWithData(path, stream, contentEntry);
- decompilerService.OpenServerDecompiler(contentEntry.ServerName.ToRobustUrl());
- }
-
- protected override void Initialise()
- {
- }
-
- protected override void InitialiseInDesignMode()
- {
- }
-}
\ No newline at end of file
diff --git a/Nebula.Launcher/ViewModels/MainViewModel.cs b/Nebula.Launcher/ViewModels/MainViewModel.cs
index df733f8..b8ee4f8 100644
--- a/Nebula.Launcher/ViewModels/MainViewModel.cs
+++ b/Nebula.Launcher/ViewModels/MainViewModel.cs
@@ -5,6 +5,7 @@ using System.IO;
using System.Linq;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Nebula.Launcher.Models;
using Nebula.Launcher.Services;
using Nebula.Launcher.ViewModels.Pages;
using Nebula.Launcher.ViewModels.Popup;
@@ -22,11 +23,10 @@ public partial class MainViewModel : ViewModelBase
{
private readonly List _templates =
[
- new ListItemTemplate(typeof(AccountInfoViewModel), "user", "Account", null),
- new ListItemTemplate(typeof(ServerListViewModel), "file", "Servers", false),
- new ListItemTemplate(typeof(ServerListViewModel), "star", "Favorites", true),
- new ListItemTemplate(typeof(ContentBrowserViewModel), "folder", "Content", null),
- new ListItemTemplate(typeof(ConfigurationViewModel), "settings", "Settings", null)
+ new ListItemTemplate(typeof(AccountInfoViewModel), "user", "Account"),
+ new ListItemTemplate(typeof(ServerOverviewModel), "file", "Servers"),
+ new ListItemTemplate(typeof(ContentBrowserViewModel), "folder", "Content"),
+ new ListItemTemplate(typeof(ConfigurationViewModel), "settings", "Settings")
];
private readonly List _viewQueue = new();
@@ -93,19 +93,19 @@ public partial class MainViewModel : ViewModelBase
if (!ViewHelperService.TryGetViewModel(value.ModelType, out var vmb)) return;
- OpenPage(vmb, value.args, false);
+ OpenPage(vmb, false);
}
- public T RequirePage() where T : ViewModelBase, IViewModelPage
+ public T RequirePage() where T : ViewModelBase
{
if (CurrentPage is T vam) return vam;
var page = ViewHelperService.GetViewModel();
- OpenPage(page, null);
+ OpenPage(page);
return page;
}
- private void OpenPage(ViewModelBase obj, object? args, bool selectListView = true)
+ private void OpenPage(ViewModelBase obj, bool selectListView = true)
{
var tabItems = Items.Where(vm => vm.ModelType == obj.GetType());
@@ -118,11 +118,6 @@ public partial class MainViewModel : ViewModelBase
}
}
- if (obj is IViewModelPage page)
- {
- page.OnPageOpen(args);
- }
-
CurrentPage = obj;
}
diff --git a/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs b/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs
index 9d99ccf..b2f3cc4 100644
--- a/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs
+++ b/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs
@@ -19,7 +19,7 @@ namespace Nebula.Launcher.ViewModels.Pages;
[ViewModelRegister(typeof(AccountInfoView))]
[ConstructGenerator]
-public partial class AccountInfoViewModel : ViewModelBase, IViewModelPage
+public partial class AccountInfoViewModel : ViewModelBase
{
[ObservableProperty] private bool _authMenuExpand;
@@ -255,11 +255,8 @@ public partial class AccountInfoViewModel : ViewModelBase, IViewModelPage
ConfigurationService.SetConfigValue(LauncherConVar.AuthProfiles,
Accounts.ToArray());
}
-
- public void OnPageOpen(object? args)
- {
- }
}
+
public sealed record ProfileAuthCredentials(
string Login,
string Password,
diff --git a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs
index 1fa4c9d..9979e16 100644
--- a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs
+++ b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs
@@ -119,7 +119,7 @@ public partial class StringConfigurationViewModel : ViewModelBase , IConfigurati
private string _oldText = string.Empty;
- public ConfigContext Context { get; set; }
+ public required ConfigContext Context { get; set; }
public void InitializeConfig()
{
ConfigName = Context.ConVar.Name;
diff --git a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs
index 62007c7..cbbbc1a 100644
--- a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs
+++ b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs
@@ -1,397 +1,462 @@
using System;
-using System.Collections.Frozen;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
+using Microsoft.Extensions.DependencyInjection;
+using Nebula.Launcher.Models;
using Nebula.Launcher.Services;
-using Nebula.Launcher.ViewModels.ContentView;
using Nebula.Launcher.ViewModels.Popup;
+using Nebula.Launcher.Views;
using Nebula.Launcher.Views.Pages;
using Nebula.Shared.FileApis;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
-using Nebula.Shared.Services.Logging;
using Nebula.Shared.Utils;
namespace Nebula.Launcher.ViewModels.Pages;
[ViewModelRegister(typeof(ContentBrowserView))]
[ConstructGenerator]
-public sealed partial class ContentBrowserViewModel : ViewModelBase , IViewModelPage
+public sealed partial class ContentBrowserViewModel : ViewModelBase, IContentHolder
{
- private readonly List _root = new();
-
- private readonly List _history = new();
-
- [ObservableProperty] private string _message = "";
- [ObservableProperty] private string _searchText = "";
-
- private ContentEntry? _selectedEntry;
- private ILogger _logger;
+ [ObservableProperty] private IContentEntry _currentEntry;
[ObservableProperty] private string _serverText = "";
- [ObservableProperty] private ContentViewBase? _contentView;
- public bool IsCustomContenView => ContentView != null;
-
-
+ [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 HubService HubService { get; } = default!;
- [GenerateProperty] private IServiceProvider ServiceProvider {get;}
+ [GenerateProperty] private IServiceProvider ServiceProvider { get; }
[GenerateProperty, DesignConstruct] private ViewHelperService ViewHelperService { get; } = default!;
- public ObservableCollection Entries { get; } = new();
-
- private Dictionary _contentContainers = new();
-
- public ContentEntry? SelectedEntry
- {
- get => _selectedEntry;
- set
- {
- var oldSearchText = SearchText;
- SearchText = value?.GetPath().ToString() ?? "";
-
- ContentView = null;
- Entries.Clear();
- _selectedEntry = value;
-
- if (value == null) return;
-
- if(value.TryOpen(out var stream, out var item)){
-
- var ext = Path.GetExtension(item.Value.Path);
- var myTempFile = Path.Combine(Path.GetTempPath(), "tempie" + ext);
-
- if(TryGetContentViewer(ext, out var contentViewBase)){
- _logger.Debug($"Opening custom context:{item.Value.Path}");
- contentViewBase.InitialiseWithData(value.GetPath(), stream, value);
- stream.Dispose();
- ContentView = contentViewBase;
- return;
- }
-
- var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None);
- stream.CopyTo(sw);
-
- sw.Dispose();
- stream.Dispose();
-
- var startInfo = new ProcessStartInfo(myTempFile)
- {
- UseShellExecute = true
- };
-
-
- _logger.Log("Opening " + myTempFile);
- Process.Start(startInfo);
-
- return;
- }
-
- if(SearchText.Length > oldSearchText.Length)
- AppendHistory(oldSearchText);
-
- foreach (var entryCh in value.Childs) Entries.Add(entryCh);
- }
- }
-
- private bool TryGetContentViewer(string type,[NotNullWhen(true)] out ContentViewBase? contentViewBase){
- contentViewBase = null;
- if(!_contentContainers.TryGetValue(type, out var contentViewType) ||
- !contentViewType.IsAssignableTo(typeof(ContentViewBase)))
- return false;
-
- contentViewBase = (ContentViewBase)ServiceProvider.GetService(contentViewType)!;
- return true;
- }
-
-
- protected override void InitialiseInDesignMode()
- {
- var a = new ContentEntry(this, "A:", "A", "", default!);
- var b = new ContentEntry(this, "B", "B", "", default!);
- a.TryAddChild(b);
- Entries.Add(a);
- }
-
- protected override void Initialise()
- {
- _logger = DebugService.GetLogger(this);
- FillRoot(HubService.ServerList);
-
- HubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
- HubService.HubServerLoaded += GoHome;
-
- if (!HubService.IsUpdating) GoHome();
-
- _contentContainers.Add(".dll",typeof(DecompilerContentView));
- }
-
- private void GoHome()
- {
- SelectedEntry = null;
- foreach (var entry in _root) Entries.Add(entry);
- }
-
- private void HubServerChangedEventArgs(HubServerChangedEventArgs obj)
- {
- if (obj.Action == HubServerChangeAction.Clear) _root.Clear();
- if (obj.Action == HubServerChangeAction.Add) FillRoot(obj.Items);
- }
-
- private void FillRoot(IEnumerable infos)
- {
- foreach (var info in infos)
- _root.Add(new ContentEntry(this, info.StatusData.Name, info.Address, info.Address, default!));
- }
-
- public void Go(string server, ContentPath path)
- {
- ServerText = server;
- Go(path);
- }
-
- public async void Go(ContentPath path)
- {
- if (path.Pathes.Count > 0 && (path.Pathes[0].StartsWith("ss14://") || path.Pathes[0].StartsWith("ss14s://")))
- {
- ServerText = path.Pathes[0];
- path = new ContentPath("");
- }
-
- if (string.IsNullOrEmpty(ServerText))
- {
- SearchText = "";
- GoHome();
- return;
- }
-
- if (ServerText != SelectedEntry?.ServerName) SelectedEntry = await CreateEntry(ServerText);
-
- _logger.Debug("Going to:" + path.Path);
-
- var oriPath = path.Clone();
- try
- {
- if (SelectedEntry == null || !SelectedEntry.GetRoot().TryGetEntry(path, out var centry))
- throw new Exception("Not found! " + oriPath.Path);
-
- SelectedEntry = centry;
- }
- catch (Exception e)
- {
- PopupService.Popup(e);
- }
- }
-
public void OnBackEnter()
{
- Go(new ContentPath(GetHistory()));
- }
-
- public void OnGoEnter()
- {
- Go(new ContentPath(SearchText));
+ CurrentEntry.Parent?.GoCurrent();
}
public void OnUnpack()
{
- if (SelectedEntry == null) return;
+ if(CurrentEntry is not ServerFolderContentEntry serverEntry)
+ return;
+
var myTempDir = FileService.EnsureTempDir(out var tmpDir);
var loading = ViewHelperService.GetViewModel();
loading.LoadingName = "Unpacking entry";
PopupService.Popup(loading);
- Task.Run(() => ContentService.Unpack(SelectedEntry.FileApi, myTempDir, loading));
+ Task.Run(() => ContentService.Unpack(serverEntry.FileApi, myTempDir, loading));
var startInfo = new ProcessStartInfo(){
FileName = "explorer.exe",
Arguments = tmpDir,
};
- _logger.Log("Opening " + tmpDir);
+
Process.Start(startInfo);
}
- private async Task CreateEntry(string serverUrl)
+ public void OnGoEnter()
{
- var loading = ViewHelperService.GetViewModel();
- loading.LoadingName = "Loading entry";
- PopupService.Popup(loading);
-
- var rurl = serverUrl.ToRobustUrl();
- var info = await ContentService.GetBuildInfo(rurl, CancellationService.Token);
- var hashApi = await ContentService.EnsureItems(info.RobustManifestInfo, loading,
- CancellationService.Token);
-
- var rootEntry = new ContentEntry(this, "", "", serverUrl, hashApi);
-
- foreach (var item in hashApi.Manifest.Values)
+ if (string.IsNullOrWhiteSpace(ServerText))
{
- var path = new ContentPath(item.Path);
- rootEntry.CreateItem(path, item);
+ SetHubRoot();
+ SearchText = string.Empty;
+ return;
}
- loading.Dispose();
-
- return rootEntry;
+ try
+ {
+ var cur = ServiceProvider.GetService()!;
+ cur.Init(this, ServerText.ToRobustUrl());
+ var curContent = cur.Go(new ContentPath(SearchText));
+ if(curContent == null)
+ throw new NullReferenceException($"{SearchText} not found in {ServerText}");
+
+ CurrentEntry = curContent;
+ }
+ catch (Exception e)
+ {
+ PopupService.Popup(e);
+ ServerText = string.Empty;
+ SearchText = string.Empty;
+ SetHubRoot();
+ }
}
- private void AppendHistory(string str)
+ partial void OnCurrentEntryChanged(IContentEntry value)
{
- if (_history.Count >= 10) _history.RemoveAt(9);
- _history.Insert(0, str);
+ SearchText = value.FullPath.ToString();
+ if (value.GetRoot() is ServerFolderContentEntry serverEntry)
+ {
+ ServerText = serverEntry.ServerUrl.ToString();
+ }
+ }
+
+ protected override void InitialiseInDesignMode()
+ {
+ var root = ViewHelperService.GetViewModel();
+ root.Init(this);
+ var child = root.AddFolder("Biba");
+ child.AddFolder("Boba");
+ child.AddFolder("Buba");
+ CurrentEntry = root;
}
- private string GetHistory()
+ protected override void Initialise()
{
- if (_history.Count == 0) return "";
- var h = _history[0];
- _history.RemoveAt(0);
- return h;
+ SetHubRoot();
}
- public void OnPageOpen(object? args)
+ public void SetHubRoot()
{
+ var root = ViewHelperService.GetViewModel();
+ root.InitHubList(this);
+ CurrentEntry = root;
+ }
+
+ public void Go(RobustUrl url, ContentPath path)
+ {
+ ServerText = url.ToString();
+ SearchText = path.ToString();
+ OnGoEnter();
}
}
-public class ContentEntry
+public interface IContentHolder
{
- private readonly Dictionary _childs = new();
+ public IContentEntry CurrentEntry { get; set; }
+}
+
+public interface IContentEntry
+{
+ public IContentHolder Holder { get; }
- private readonly ContentBrowserViewModel _viewModel;
- public readonly HashApi FileApi;
- public readonly RobustManifestItem? Item;
-
- internal ContentEntry(ContentBrowserViewModel viewModel, string name, string pathName, string serverName, HashApi fileApi, RobustManifestItem? item = null)
+ public IContentEntry? Parent { get; set; }
+ public string? Name { get; }
+ public string IconPath { get; }
+ public ContentPath FullPath => Parent?.FullPath.With(Name) ?? new ContentPath(Name);
+
+ public IContentEntry? Go(ContentPath path);
+
+ public void GoCurrent()
{
+ var entry = Go(ContentPath.Empty);
+ if(entry is not null) Holder.CurrentEntry = entry;
+ }
+
+ public IContentEntry GetRoot()
+ {
+ if (Parent is null) return this;
+ return Parent.GetRoot();
+ }
+}
+
+
+public sealed class LazyContentEntry : IContentEntry
+{
+ public IContentHolder Holder { get; set; }
+ public IContentEntry? Parent { get; set; }
+ public string? Name { get; }
+ public string IconPath { get; }
+
+ private readonly IContentEntry _lazyEntry;
+ private readonly Action _lazyEntryInit;
+
+ public LazyContentEntry (IContentHolder holder,string name, IContentEntry entry, Action lazyEntryInit)
+ {
+ Holder = holder;
Name = name;
- ServerName = serverName;
- PathName = pathName;
- _viewModel = viewModel;
- FileApi = fileApi;
- Item = item;
+ IconPath = entry.IconPath;
+ _lazyEntry = entry;
+ _lazyEntryInit = lazyEntryInit;
}
-
- public bool IsDirectory => Item == null;
-
- public string Name { get; private set; }
- public string PathName { get; }
- public string ServerName { get; }
- public string IconPath { get; set; } = "/Assets/svg/folder.svg";
-
- public ContentEntry? Parent { get; private set; }
- public bool IsRoot => Parent == null;
-
- //TODO: Remove LINQ later...
- public IReadOnlyList Childs => _childs.Values.OrderBy(v => v,new ContentComparer()).ToList();
-
- public bool TryOpen([NotNullWhen(true)] out Stream? stream,[NotNullWhen(true)] out RobustManifestItem? item){
- stream = null;
- item = null;
- if(Item is null || !FileApi.TryOpen(Item.Value, out stream))
- return false;
-
- item = Item;
- return true;
- }
-
- public bool TryGetChild(string name, [NotNullWhen(true)] out ContentEntry? child)
+ public IContentEntry? Go(ContentPath path)
{
- return _childs.TryGetValue(name, out child);
+ _lazyEntryInit?.Invoke();
+ return _lazyEntry;
+ }
+}
+
+public sealed class ExtContentExecutor
+{
+ public ServerFolderContentEntry _root;
+ private DecompilerService _decompilerService;
+
+ public ExtContentExecutor(ServerFolderContentEntry root, DecompilerService decompilerService)
+ {
+ _root = root;
+ _decompilerService = decompilerService;
}
- public bool TryAddChild(ContentEntry contentEntry)
+ public bool TryExecute(RobustManifestItem manifestItem)
{
- if (_childs.TryAdd(contentEntry.PathName, contentEntry))
+ var ext = Path.GetExtension(manifestItem.Path);
+
+ if (ext == ".dll")
{
- contentEntry.Parent = this;
+ _decompilerService.OpenServerDecompiler(_root.ServerUrl);
return true;
}
return false;
}
+}
- public ContentPath GetPath()
+
+public sealed partial class ManifestContentEntry : IContentEntry
+{
+ public IContentHolder Holder { get; set; } = default!;
+ public IContentEntry? Parent { get; set; }
+ public string? Name { get; set; }
+ public string IconPath => "/Assets/svg/file.svg";
+
+ private RobustManifestItem _manifestItem;
+ private HashApi _hashApi = default!;
+ private ExtContentExecutor _extContentExecutor = default!;
+
+ public void Init(IContentHolder holder, RobustManifestItem manifestItem, HashApi api, ExtContentExecutor executor)
{
- if (Parent != null)
+ Holder = holder;
+ Name = new ContentPath(manifestItem.Path).GetName();
+ _manifestItem = manifestItem;
+ _hashApi = api;
+ _extContentExecutor = executor;
+ }
+
+ public IContentEntry? Go(ContentPath path)
+ {
+ if (_extContentExecutor.TryExecute(_manifestItem))
+ return null;
+
+ var ext = Path.GetExtension(_manifestItem.Path);
+
+ try
{
- var path = Parent.GetPath();
- path.Pathes.Add(PathName);
- return path;
+ if (!_hashApi.TryOpen(_manifestItem, out var stream))
+ return null;
+
+
+ var myTempFile = Path.Combine(Path.GetTempPath(), "tempie" + ext);
+
+
+ var sw = new FileStream(myTempFile, FileMode.Create, FileAccess.Write, FileShare.None);
+ stream.CopyTo(sw);
+
+ sw.Dispose();
+ stream.Dispose();
+
+ var startInfo = new ProcessStartInfo(myTempFile)
+ {
+ UseShellExecute = true
+ };
+
+ Process.Start(startInfo);
}
-
- return new ContentPath([PathName]);
- }
-
- public ContentEntry GetOrCreateDirectory(ContentPath rootPath)
- {
- if (rootPath.Pathes.Count == 0) return this;
-
- var fName = rootPath.GetNext();
-
- if (!TryGetChild(fName, out var child))
+ catch (Exception e)
{
- child = new ContentEntry(_viewModel, fName, fName, ServerName, FileApi);
- TryAddChild(child);
+ _extContentExecutor._root.PopupService.Popup(e);
}
-
- return child.GetOrCreateDirectory(rootPath);
- }
-
- public ContentEntry GetRoot()
- {
- if (Parent == null) return this;
- return Parent.GetRoot();
- }
-
- public ContentEntry CreateItem(ContentPath path, RobustManifestItem item)
- {
- var dir = path.GetDirectory();
- var dirEntry = GetOrCreateDirectory(dir);
-
- var name = path.GetName();
- var entry = new ContentEntry(_viewModel, name, name, ServerName, FileApi, item);
-
- dirEntry.TryAddChild(entry);
- entry.IconPath = "/Assets/svg/file.svg";
- return entry;
- }
-
- public bool TryGetEntry(ContentPath path, out ContentEntry? entry)
- {
- entry = null;
-
- if (path.Pathes.Count == 0)
- {
- entry = this;
- return true;
- }
-
- var fName = path.GetNext();
-
- if (!TryGetChild(fName, out var child)) return false;
-
- return child.TryGetEntry(path, out entry);
- }
-
- public void OnPathGo()
- {
- _viewModel.Go(GetPath());
+ return null;
}
}
-public struct ContentPath
+[ViewModelRegister(typeof(FileContentEntryView), false), ConstructGenerator]
+public sealed partial class FolderContentEntry : BaseFolderContentEntry
{
+ [GenerateProperty, DesignConstruct] public override ViewHelperService ViewHelperService { get; } = default!;
+
+ public FolderContentEntry AddFolder(string folderName)
+ {
+ var folder = ViewHelperService.GetViewModel();
+ folder.Init(Holder, folderName);
+ return AddChild(folder);
+ }
+
+ protected override void InitialiseInDesignMode() { }
+ protected override void Initialise() { }
+}
+
+[ViewModelRegister(typeof(FileContentEntryView), false), ConstructGenerator]
+public sealed partial class ServerFolderContentEntry : BaseFolderContentEntry
+{
+ [GenerateProperty, DesignConstruct] public override ViewHelperService ViewHelperService { get; } = default!;
+ [GenerateProperty] public ContentService ContentService { get; } = default!;
+ [GenerateProperty] public CancellationService CancellationService { get; } = default!;
+ [GenerateProperty] public PopupMessageService PopupService { get; } = default!;
+ [GenerateProperty] public DecompilerService DecompilerService { get; } = default!;
+
+ public RobustUrl ServerUrl { get; private set; }
+
+ public HashApi FileApi { get; private set; } = default!;
+
+ private ExtContentExecutor _contentExecutor = default!;
+
+ public void Init(IContentHolder holder, RobustUrl serverUrl)
+ {
+ base.Init(holder);
+ _contentExecutor = new ExtContentExecutor(this, DecompilerService);
+ IsLoading = true;
+ var loading = ViewHelperService.GetViewModel();
+ loading.LoadingName = "Loading entry";
+ PopupService.Popup(loading);
+ ServerUrl = serverUrl;
+
+ Task.Run(async () =>
+ {
+ var buildInfo = await ContentService.GetBuildInfo(serverUrl, CancellationService.Token);
+ FileApi = await ContentService.EnsureItems(buildInfo.RobustManifestInfo, loading,
+ CancellationService.Token);
+
+ foreach (var (path, item) in FileApi.Manifest)
+ {
+ CreateContent(new ContentPath(path), item);
+ }
+
+ IsLoading = false;
+ loading.Dispose();
+ });
+ }
+
+ public ManifestContentEntry CreateContent(ContentPath path, RobustManifestItem manifestItem)
+ {
+ var pathDir = path.GetDirectory();
+ BaseFolderContentEntry parent = this;
+
+ while (pathDir.TryNext(out var dirPart))
+ {
+ if (!parent.TryGetChild(dirPart, out var folderContentEntry))
+ {
+ folderContentEntry = ViewHelperService.GetViewModel();
+ ((FolderContentEntry)folderContentEntry).Init(Holder, dirPart);
+ parent.AddChild(folderContentEntry);
+ }
+
+ parent = folderContentEntry as BaseFolderContentEntry ?? throw new InvalidOperationException();
+ }
+
+ var manifestContent = new ManifestContentEntry();
+ manifestContent.Init(Holder, manifestItem, FileApi, _contentExecutor);
+
+ parent.AddChild(manifestContent);
+
+ return manifestContent;
+ }
+
+ protected override void InitialiseInDesignMode() { }
+ protected override void Initialise() { }
+}
+
+[ViewModelRegister(typeof(FileContentEntryView), false), ConstructGenerator]
+public sealed partial class ServerListContentEntry : BaseFolderContentEntry
+{
+ [GenerateProperty, DesignConstruct] public override ViewHelperService ViewHelperService { get; } = default!;
+ [GenerateProperty] public ConfigurationService ConfigurationService { get; } = default!;
+ [GenerateProperty] public IServiceProvider ServiceProvider { get; } = default!;
+ [GenerateProperty] public RestService RestService { get; } = default!;
+
+
+ public void InitHubList(IContentHolder holder)
+ {
+ base.Init(holder);
+
+ var servers = ConfigurationService.GetConfigValue(LauncherConVar.Hub)!;
+
+ foreach (var server in servers)
+ {
+ var serverFolder = ServiceProvider.GetService()!;
+ var serverLazy = new LazyContentEntry(Holder, server.Name , serverFolder, () => serverFolder.InitServerList(Holder, server));
+ AddChild(serverLazy);
+ }
+ }
+
+ public async void InitServerList(IContentHolder holder, ServerHubRecord hubRecord)
+ {
+ base.Init(holder, hubRecord.Name);
+
+ IsLoading = true;
+ var servers =
+ await RestService.GetAsync>(new Uri(hubRecord.MainUrl), CancellationToken.None);
+
+ foreach (var server in servers)
+ {
+ var serverFolder = ServiceProvider.GetService()!;
+ var serverLazy = new LazyContentEntry(Holder, server.StatusData.Name , serverFolder, () => serverFolder.Init(Holder, server.Address.ToRobustUrl()));
+ AddChild(serverLazy);
+ }
+
+ IsLoading = true;
+ }
+
+
+
+ protected override void InitialiseInDesignMode()
+ {
+ }
+
+ protected override void Initialise()
+ {
+ }
+}
+
+public abstract class BaseFolderContentEntry : ViewModelBase, IContentEntry
+{
+ public bool IsLoading { get; set; } = false;
+ public abstract ViewHelperService ViewHelperService { get; }
+
+ public ObservableCollection Entries { get; } = [];
+
+ private Dictionary _childs = [];
+
+ public string IconPath => "/Assets/svg/folder.svg";
+ public IContentHolder Holder { get; private set; }
+ public IContentEntry? Parent { get; set; }
+ public string? Name { get; private set; }
+
+ public IContentEntry? Go(ContentPath path)
+ {
+ if (path.IsEmpty()) return this;
+ if (_childs.TryGetValue(path.GetNext(), out var child))
+ return child.Go(path);
+
+ return null;
+ }
+
+ public void Init(IContentHolder holder, string? name = null)
+ {
+ Name = name;
+ Holder = holder;
+ }
+
+ public T AddChild(T child) where T: IContentEntry
+ {
+ if(child.Name is null) throw new InvalidOperationException();
+
+ child.Parent = this;
+
+ _childs.Add(child.Name, child);
+ Entries.Add(child);
+
+ return child;
+ }
+
+ public bool TryGetChild(string name,[NotNullWhen(true)] out IContentEntry? child)
+ {
+ return _childs.TryGetValue(name, out child);
+ }
+}
+
+
+public struct ContentPath : IEquatable
+{
+ public static readonly ContentPath Empty = new();
+
public List Pathes { get; }
public ContentPath()
@@ -404,17 +469,23 @@ public struct ContentPath
Pathes = pathes;
}
- public ContentPath(string path)
+ public ContentPath(string? path)
{
Pathes = string.IsNullOrEmpty(path)
? new List()
- : path.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries).ToList();
+ : path.Split(['/'], StringSplitOptions.RemoveEmptyEntries).ToList();
+ }
+
+ public ContentPath With(string? name)
+ {
+ if (name != null) return new ContentPath([..Pathes, name]);
+ return new ContentPath(Pathes);
}
public ContentPath GetDirectory()
{
if (Pathes.Count == 0)
- return this; // Root remains root when getting the directory.
+ return this;
var directoryPathes = Pathes.Take(Pathes.Count - 1).ToList();
return new ContentPath(directoryPathes);
@@ -439,6 +510,14 @@ public struct ContentPath
return string.IsNullOrWhiteSpace(nextName) ? GetNext() : nextName;
}
+ public bool TryNext([NotNullWhen(true)]out string? part)
+ {
+ part = null;
+ if (Pathes.Count == 0) return false;
+ part = GetNext();
+ return true;
+ }
+
public ContentPath Clone()
{
return new ContentPath(new List(Pathes));
@@ -450,19 +529,35 @@ public struct ContentPath
{
return Path;
}
+
+ public bool IsEmpty()
+ {
+ return Pathes.Count == 0;
+ }
+
+ public bool Equals(ContentPath other)
+ {
+ return Pathes.Equals(other.Pathes);
+ }
+
+ public override bool Equals(object? obj)
+ {
+ return obj is ContentPath other && Equals(other);
+ }
+
+ public override int GetHashCode()
+ {
+ return Pathes.GetHashCode();
+ }
}
-public sealed class ContentComparer : IComparer
+public sealed class ContentComparer : IComparer
{
- public int Compare(ContentEntry? x, ContentEntry? y)
+ public int Compare(IContentEntry? x, IContentEntry? y)
{
if (ReferenceEquals(x, y)) return 0;
if (y is null) return 1;
if (x is null) return -1;
- var iconComparison = string.Compare(x.IconPath, y.IconPath, StringComparison.Ordinal);
- if (iconComparison != 0) return -iconComparison;
- var nameComparison = string.Compare(x.Name, y.Name, StringComparison.Ordinal);
- if (nameComparison != 0) return nameComparison;
- return string.Compare(x.ServerName, y.ServerName, StringComparison.Ordinal);
+ return string.Compare(x.Name, y.Name, StringComparison.Ordinal);
}
}
\ No newline at end of file
diff --git a/Nebula.Launcher/ViewModels/Pages/IViewModelPage.cs b/Nebula.Launcher/ViewModels/Pages/IViewModelPage.cs
deleted file mode 100644
index f2f473f..0000000
--- a/Nebula.Launcher/ViewModels/Pages/IViewModelPage.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-namespace Nebula.Launcher.ViewModels.Pages;
-
-public interface IViewModelPage
-{
- public void OnPageOpen(object? args);
-}
\ No newline at end of file
diff --git a/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.Favorite.cs b/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.Favorite.cs
deleted file mode 100644
index 42bce27..0000000
--- a/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.Favorite.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-using System.Collections.ObjectModel;
-using System.Linq;
-using Nebula.Shared.Models;
-using Nebula.Shared.Services;
-using Nebula.Shared.Utils;
-
-namespace Nebula.Launcher.ViewModels.Pages;
-
-public partial class ServerListViewModel
-{
- [GenerateProperty] private ConfigurationService ConfigurationService { get; }
- [GenerateProperty] private RestService RestService { get; }
-
- public ObservableCollection FavoriteServers { get; } = [];
-
- private void UpdateFavoriteEntries()
- {
- foreach(var fav in FavoriteServers.ToList()){
- FavoriteServers.Remove(fav);
- }
-
- var servers = ConfigurationService.GetConfigValue(LauncherConVar.Favorites);
- if (servers is null || servers.Length == 0)
- {
- return;
- }
-
- foreach (var server in servers)
- {
- var s = ServerViewContainer.Get(server.ToRobustUrl());
- s.IsFavorite = true;
- FavoriteServers.Add(s);
- }
-
- ApplyFilter();
- }
-
- public void AddFavorite(ServerEntryModelView entryModelView)
- {
- entryModelView.IsFavorite = true;
- AddFavorite(entryModelView.Address);
- }
-
- public void AddFavorite(RobustUrl robustUrl)
- {
- var servers = (ConfigurationService.GetConfigValue(LauncherConVar.Favorites) ?? []).ToList();
- servers.Add(robustUrl.ToString());
- ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray());
- UpdateFavoriteEntries();
- }
-
- public void RemoveFavorite(ServerEntryModelView entryModelView)
- {
- var servers = (ConfigurationService.GetConfigValue(LauncherConVar.Favorites) ?? []).ToList();
- servers.Remove(entryModelView.Address.ToString());
- ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray());
- entryModelView.IsFavorite = false;
- UpdateFavoriteEntries();
- }
-}
\ No newline at end of file
diff --git a/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs
similarity index 55%
rename from Nebula.Launcher/ViewModels/Pages/ServerListViewModel.cs
rename to Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs
index 54d23ae..8ade626 100644
--- a/Nebula.Launcher/ViewModels/Pages/ServerListViewModel.cs
+++ b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs
@@ -1,67 +1,74 @@
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
-using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
+using Microsoft.Extensions.DependencyInjection;
using Nebula.Launcher.Controls;
+using Nebula.Launcher.Models;
+using Nebula.Launcher.ServerListProviders;
using Nebula.Launcher.Services;
-using Nebula.Launcher.ViewModels.Popup;
-using Nebula.Launcher.Views;
using Nebula.Launcher.Views.Pages;
+using Nebula.Shared;
using Nebula.Shared.Models;
using Nebula.Shared.Services;
-using Nebula.Shared.Utils;
namespace Nebula.Launcher.ViewModels.Pages;
-[ViewModelRegister(typeof(ServerListView))]
+[ViewModelRegister(typeof(ServerOverviewView))]
[ConstructGenerator]
-public partial class ServerListViewModel : ViewModelBase, IViewModelPage
+public partial class ServerOverviewModel : ViewModelBase
{
[ObservableProperty] private string _searchText = string.Empty;
-
- [ObservableProperty] private bool _isFavoriteMode;
[ObservableProperty] private bool _isFilterVisible;
+
+ [ObservableProperty] private ServerListView _currentServerList = new ServerListView();
- public ObservableCollection Servers { get; }= new();
- public ObservableCollection HubErrors { get; } = new();
public readonly ServerFilter CurrentFilter = new ServerFilter();
public Action? OnSearchChange;
- [GenerateProperty] private HubService HubService { get; }
+
[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; }
- private ServerViewContainer ServerViewContainer { get; set; }
+ public ObservableCollection Items { get; private set; }
+ [ObservableProperty] private ServerListTabTemplate _selectedItem;
+
+ [GenerateProperty, DesignConstruct] private ServerViewContainer ServerViewContainer { get; set; }
+
+ private Dictionary _viewCache = [];
- private List UnsortedServers { get; } = new();
//Design think
protected override void InitialiseInDesignMode()
{
- ServerViewContainer = new ServerViewContainer(this, ViewHelperService);
- HubErrors.Add(new Exception("UVI"));
+ Items = new ObservableCollection([
+ new ServerListTabTemplate(new TestServerList(), "Test think"),
+ new ServerListTabTemplate(new TestServerList(), "Test think2")
+ ]);
+ SelectedItem = Items[0];
}
//real think
protected override void Initialise()
{
- ServerViewContainer = new ServerViewContainer(this, ViewHelperService);
+ var tempItems = new List();
+ foreach (var record in ConfigurationService.GetConfigValue(LauncherConVar.Hub) ?? [])
+ {
+ tempItems.Add(new ServerListTabTemplate(ServiceProvider.GetService()!.With(record.MainUrl), record.Name));
+ }
- foreach (var info in HubService.ServerList) UnsortedServers.Add(info);
-
- HubService.HubServerChangedEventArgs += HubServerChangedEventArgs;
- HubService.HubServerLoaded += UpdateServerEntries;
- HubService.HubServerLoadingError += HubServerLoadingError;
- OnSearchChange += OnChangeSearch;
-
- if (!HubService.IsUpdating) UpdateServerEntries();
- UpdateFavoriteEntries();
+ tempItems.Add(new ServerListTabTemplate(FavoriteServerListProvider, "Favorite"));
+
+ Items = new ObservableCollection(tempItems);
+
+ SelectedItem = Items[0];
}
public void ApplyFilter()
@@ -80,83 +87,45 @@ public partial class ServerListViewModel : ViewModelBase, IViewModelPage
CurrentFilter.Tags.Remove(args.Tag);
ApplyFilter();
}
-
- private void HubServerLoadingError(Exception obj)
- {
- HubErrors.Add(obj);
- }
-
- private void UpdateServerEntries()
- {
- foreach(var fav in Servers.ToList()){
- Servers.Remove(fav);
- }
-
- Task.Run(() =>
- {
- UnsortedServers.Sort(new ServerComparer());
- foreach (var info in UnsortedServers)
- {
- var view = ServerViewContainer.Get(info.Address.ToRobustUrl(), info.StatusData);
- Servers.Add(view);
- }
- ApplyFilter();
- });
- }
-
- private void OnChangeSearch()
- {
- CurrentFilter.SearchText = SearchText;
- ApplyFilter();
- }
-
- private void HubServerChangedEventArgs(HubServerChangedEventArgs obj)
- {
- if (obj.Action == HubServerChangeAction.Add)
- foreach (var info in obj.Items)
- UnsortedServers.Add(info);
- if (obj.Action == HubServerChangeAction.Remove)
- foreach (var info in obj.Items)
- UnsortedServers.Remove(info);
- if (obj.Action == HubServerChangeAction.Clear)
- {
- UnsortedServers.Clear();
- ServerViewContainer.Clear();
- UpdateFavoriteEntries();
- }
- }
-
+
public void FilterRequired()
{
IsFilterVisible = !IsFilterVisible;
}
- public void AddFavoriteRequired()
- {
- var p = ViewHelperService.GetViewModel();
- PopupMessageService.Popup(p);
- }
-
public void UpdateRequired()
{
- HubErrors.Clear();
- Task.Run(HubService.UpdateHub);
+ CurrentServerList.RefreshFromProvider();
}
- public void OnPageOpen(object? args)
+ partial void OnSelectedItemChanged(ServerListTabTemplate value)
{
- if (args is bool fav)
+ if (!_viewCache.TryGetValue(value.TabName, out var view))
{
- IsFavoriteMode = fav;
+ view = ServerListView.TakeFrom(value.ServerListProvider);
+ _viewCache[value.TabName] = view;
}
+
+ CurrentServerList = view;
}
+
}
-public class ServerViewContainer(
- ServerListViewModel serverListViewModel,
- ViewHelperService viewHelperService
- )
+[ServiceRegister]
+public class ServerViewContainer
{
+ private readonly ViewHelperService _viewHelperService;
+
+ public ServerViewContainer()
+ {
+ _viewHelperService = new ViewHelperService();
+ }
+
+ public ServerViewContainer(ViewHelperService viewHelperService)
+ {
+ _viewHelperService = viewHelperService;
+ }
+
private readonly Dictionary _entries = new();
public ICollection Items => _entries.Values;
@@ -177,16 +146,12 @@ public class ServerViewContainer(
return entry;
}
- entry = viewHelperService.GetViewModel().WithData(url, serverStatus);
+ entry = _viewHelperService.GetViewModel().WithData(url, serverStatus);
_entries.Add(url.ToString(), entry);
}
- entry.OnFavoriteToggle += () =>
- {
- if (entry.IsFavorite) serverListViewModel.RemoveFavorite(entry);
- else serverListViewModel.AddFavorite(entry);
- };
+ Console.WriteLine("LENGTH OF PENIS IS: " + _entries.Count);
return entry;
}
diff --git a/Nebula.Launcher/ViewModels/Popup/AddFavoriteViewModel.cs b/Nebula.Launcher/ViewModels/Popup/AddFavoriteViewModel.cs
index e1824e7..566e527 100644
--- a/Nebula.Launcher/ViewModels/Popup/AddFavoriteViewModel.cs
+++ b/Nebula.Launcher/ViewModels/Popup/AddFavoriteViewModel.cs
@@ -26,7 +26,7 @@ public partial class AddFavoriteViewModel : PopupViewModelBase
[GenerateProperty]
public override PopupMessageService PopupMessageService { get; }
- [GenerateProperty] private ServerListViewModel ServerListViewModel { get; }
+ [GenerateProperty] private ServerOverviewModel ServerOverviewModel { get; }
[GenerateProperty] private DebugService DebugService { get; }
public override string Title => "Add to favorite";
public override bool IsClosable => true;
@@ -39,7 +39,6 @@ public partial class AddFavoriteViewModel : PopupViewModelBase
try
{
var uri = IpInput.ToRobustUrl();
- ServerListViewModel.AddFavorite(uri);
Dispose();
}
catch (Exception e)
diff --git a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs
index f1275ce..d0c5328 100644
--- a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs
+++ b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs
@@ -1,7 +1,9 @@
using System;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
+using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
@@ -10,6 +12,7 @@ using System.Windows.Input;
using Avalonia.Controls;
using Avalonia.Media;
using CommunityToolkit.Mvvm.ComponentModel;
+using Nebula.Launcher.ServerListProviders;
using Nebula.Launcher.Services;
using Nebula.Launcher.ViewModels.Pages;
using Nebula.Launcher.ViewModels.Popup;
@@ -23,7 +26,7 @@ namespace Nebula.Launcher.ViewModels;
[ViewModelRegister(typeof(ServerEntryView), false)]
[ConstructGenerator]
-public partial class ServerEntryModelView : ViewModelBase
+public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer
{
[ObservableProperty] private string _description = "Fetching info...";
[ObservableProperty] private bool _expandInfo;
@@ -39,11 +42,11 @@ public partial class ServerEntryModelView : ViewModelBase
[ObservableProperty] private bool _tagDataVisible;
public LogPopupModelView CurrLog;
- public Action? OnFavoriteToggle;
public RobustUrl Address { get; private set; }
[GenerateProperty] private AuthService AuthService { get; } = default!;
[GenerateProperty] private ContentService ContentService { get; } = default!;
+ [GenerateProperty] private ConfigurationService ConfigurationService { get; } = default!;
[GenerateProperty] private CancellationService CancellationService { get; } = default!;
[GenerateProperty] private DebugService DebugService { get; } = default!;
[GenerateProperty] private RunnerService RunnerService { get; } = default!;
@@ -51,6 +54,7 @@ public partial class ServerEntryModelView : ViewModelBase
[GenerateProperty] private ViewHelperService ViewHelperService { get; } = default!;
[GenerateProperty] private RestService RestService { get; } = default!;
[GenerateProperty] private MainViewModel MainViewModel { get; } = default!;
+ [GenerateProperty] private FavoriteServerListProvider FavoriteServerListProvider { get; } = default!;
public ServerStatus Status { get; private set; } =
new(
@@ -116,8 +120,14 @@ public partial class ServerEntryModelView : ViewModelBase
CurrLog = ViewHelperService.GetViewModel();
}
- public void ProcessFilter(ServerFilter serverFilter)
+ public void ProcessFilter(ServerFilter? serverFilter)
{
+ if (serverFilter == null)
+ {
+ IsVisible = true;
+ return;
+ }
+
IsVisible = serverFilter.IsMatch(Status.Name, Tags);
}
@@ -136,9 +146,16 @@ public partial class ServerEntryModelView : ViewModelBase
SetStatus(serverStatus);
else
FetchStatus();
+
+ IsFavorite = GetFavoriteEntries().Contains(Address.ToString());
return this;
}
+
+ private List GetFavoriteEntries()
+ {
+ return ConfigurationService.GetConfigValue(LauncherConVar.Favorites)?.ToList() ?? [];
+ }
private async void FetchStatus()
{
@@ -157,12 +174,16 @@ public partial class ServerEntryModelView : ViewModelBase
public void OpenContentViewer()
{
- MainViewModel.RequirePage().Go(Address.ToString(), new ContentPath());
+ MainViewModel.RequirePage().Go(Address, ContentPath.Empty);
}
public void ToggleFavorites()
{
- OnFavoriteToggle?.Invoke();
+ IsFavorite = !IsFavorite;
+ if(IsFavorite)
+ FavoriteServerListProvider.AddFavorite(this);
+ else
+ FavoriteServerListProvider.RemoveFavorite(this);
}
public void RunInstance()
@@ -188,7 +209,7 @@ public partial class ServerEntryModelView : ViewModelBase
await RunnerService.PrepareRun(buildInfo, loadingContext, CancellationService.Token);
- var path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
+ var path = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
Process = Process.Start(new ProcessStartInfo
{
@@ -372,4 +393,9 @@ public class LinkGoCommand : ICommand
}
public event EventHandler? CanExecuteChanged;
+}
+
+public interface IFilterConsumer
+{
+ public void ProcessFilter(ServerFilter? serverFilter);
}
\ No newline at end of file
diff --git a/Nebula.Launcher/Views/FileContentEntryView.axaml b/Nebula.Launcher/Views/FileContentEntryView.axaml
new file mode 100644
index 0000000..5c49963
--- /dev/null
+++ b/Nebula.Launcher/Views/FileContentEntryView.axaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Nebula.Launcher/Views/FileContentEntryView.axaml.cs b/Nebula.Launcher/Views/FileContentEntryView.axaml.cs
new file mode 100644
index 0000000..aeace0a
--- /dev/null
+++ b/Nebula.Launcher/Views/FileContentEntryView.axaml.cs
@@ -0,0 +1,22 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Nebula.Launcher.ViewModels.Pages;
+
+namespace Nebula.Launcher.Views;
+
+public partial class FileContentEntryView : UserControl
+{
+ // This constructor is used when the view is created by the XAML Previewer
+ public FileContentEntryView()
+ {
+ InitializeComponent();
+ }
+
+ // This constructor is used when the view is created via dependency injection
+ public FileContentEntryView(FolderContentEntry viewModel)
+ : this()
+ {
+ DataContext = viewModel;
+ }
+}
\ No newline at end of file
diff --git a/Nebula.Launcher/Views/MainView.axaml b/Nebula.Launcher/Views/MainView.axaml
index 2d0b758..bbe49a1 100644
--- a/Nebula.Launcher/Views/MainView.axaml
+++ b/Nebula.Launcher/Views/MainView.axaml
@@ -10,7 +10,8 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:Nebula.Shared.Models;assembly=Nebula.Shared"
xmlns:viewModels="clr-namespace:Nebula.Launcher.ViewModels"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ xmlns:models1="clr-namespace:Nebula.Launcher.Models">
@@ -63,7 +64,7 @@
Padding="0"
SelectedItem="{Binding SelectedListItem}">
-
+
-
-
-
-
-
-
-
-
+
+
+
\ No newline at end of file
diff --git a/Nebula.Launcher/Views/Pages/ServerListView.axaml b/Nebula.Launcher/Views/Pages/ServerOverviewView.axaml
similarity index 62%
rename from Nebula.Launcher/Views/Pages/ServerListView.axaml
rename to Nebula.Launcher/Views/Pages/ServerOverviewView.axaml
index 7d8700e..5b640e5 100644
--- a/Nebula.Launcher/Views/Pages/ServerListView.axaml
+++ b/Nebula.Launcher/Views/Pages/ServerOverviewView.axaml
@@ -2,41 +2,50 @@
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d"
- x:Class="Nebula.Launcher.Views.Pages.ServerListView"
- x:DataType="pages:ServerListViewModel"
+ x:Class="Nebula.Launcher.Views.Pages.ServerOverviewView"
+ x:DataType="pages:ServerOverviewModel"
xmlns="https://github.com/avaloniaui"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:pages="clr-namespace:Nebula.Launcher.ViewModels.Pages"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:controls="clr-namespace:Nebula.Launcher.Controls">
+ xmlns:controls="clr-namespace:Nebula.Launcher.Controls"
+ xmlns:models="clr-namespace:Nebula.Launcher.Models">
-
+
-
-
-
-
-
-
-
+ RowDefinitions="40,*,40">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Grid.Row="2" />
-