diff --git a/Nebula.Launcher/Assets/Style.axaml b/Nebula.Launcher/Assets/Style.axaml index 314425d..014fe4d 100644 --- a/Nebula.Launcher/Assets/Style.axaml +++ b/Nebula.Launcher/Assets/Style.axaml @@ -1,4 +1,5 @@ - + @@ -85,4 +86,41 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Nebula.Launcher/Nebula.Launcher.csproj b/Nebula.Launcher/Nebula.Launcher.csproj index 97005ba..f6e067d 100644 --- a/Nebula.Launcher/Nebula.Launcher.csproj +++ b/Nebula.Launcher/Nebula.Launcher.csproj @@ -78,8 +78,4 @@ - - - - diff --git a/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs index f8791b5..114f971 100644 --- a/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs +++ b/Nebula.Launcher/ServerListProviders/FavoriteServerListProvider.cs @@ -24,6 +24,7 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS [GenerateProperty] private ServerViewContainer ServerViewContainer { get; } private List _serverLists = []; + private string[] rawServerLists = []; public bool IsLoaded { get; private set; } public Action? OnLoaded { get; set; } @@ -67,7 +68,6 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS servers.Add(robustUrl.ToString()); ConfigurationService.SetConfigValue(LauncherConVar.Favorites, servers.ToArray()); ServerViewContainer.Get(robustUrl).IsFavorite = true; - Dirty?.Invoke(); } public void RemoveFavorite(ServerEntryModelView entryModelView) @@ -75,15 +75,31 @@ public sealed partial class FavoriteServerListProvider : IServerListProvider, IS 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() ?? []; + return rawServerLists.ToList(); } - - private void Initialise(){} + + private void Initialise() + { + ConfigurationService.SubscribeVarChanged(LauncherConVar.Favorites, OnVarChanged, true); + } + + private void OnVarChanged(string[]? value) + { + if (value == null) + { + rawServerLists = []; + Dirty?.Invoke(); + return; + } + + rawServerLists = value; + Dirty?.Invoke(); + } + private void InitialiseInDesignMode(){} } diff --git a/Nebula.Launcher/Services/ExplorerService.cs b/Nebula.Launcher/Services/ExplorerService.cs new file mode 100644 index 0000000..192c30a --- /dev/null +++ b/Nebula.Launcher/Services/ExplorerService.cs @@ -0,0 +1,34 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using Nebula.Shared; + +namespace Nebula.Launcher.Services; + + +public static class ExplorerHelper +{ + public static void OpenFolder(string path) + { + string command; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + command = "explorer.exe"; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + command = "xdg-open"; + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + command = "open"; + else + throw new PlatformNotSupportedException("Unsupported OS platform"); + + + var startInfo = new ProcessStartInfo + { + FileName = command, + Arguments = path, + UseShellExecute = false + }; + + Process.Start(startInfo); + } +} \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs index a8591c9..6beed55 100644 --- a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs @@ -2,13 +2,15 @@ using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Globalization; +using System.IO; +using System.IO.Compression; using System.Linq; using System.Reflection; using Avalonia; using Avalonia.Controls; using Avalonia.Layout; +using Nebula.Launcher.Services; using Nebula.Launcher.Views.Pages; using Nebula.Shared; using Nebula.Shared.Services; @@ -22,6 +24,8 @@ public partial class ConfigurationViewModel : ViewModelBase public ObservableCollection ConfigurationVerbose { get; } = new(); [GenerateProperty] private ConfigurationService ConfigurationService { get; } = default!; + [GenerateProperty] private PopupMessageService PopupService { get; } = default!; + [GenerateProperty] private FileService FileService { get; set; } = default!; public List<(object, Type)> ConVarList = new(); @@ -45,9 +49,40 @@ public partial class ConfigurationViewModel : ViewModelBase methodInfo.Invoke(ConfigurationService, [conVar.Item1, conVarControl.GetValue()]); } } + + public void ResetConfig() + { + foreach (var conVar in ConVarList) + { + var methodInfo = ConfigurationService.GetType().GetMethod("SetConfigValue")!.MakeGenericMethod(conVar.Item2); + methodInfo.Invoke(ConfigurationService, [conVar.Item1, null]); + } + + ConVarList.Clear(); + ConfigurationVerbose.Clear(); + + InitConfiguration(); + + PopupService.Popup("Configuration has been reset."); + } + + public void OpenDataFolder() + { + ExplorerHelper.OpenFolder(FileService.RootPath); + } + + public void ExportLogs() + { + var logPath = Path.Join(FileService.RootPath, "log"); + var path = Path.Combine(Path.GetTempPath(), "tempThink"+Path.GetRandomFileName()); + Directory.CreateDirectory(path); + + ZipFile.CreateFromDirectory(logPath, Path.Join(path, DateTime.Now.ToString("yyyy-MM-dd") + ".zip")); + ExplorerHelper.OpenFolder(path); + } - protected override void InitialiseInDesignMode() - { + private void InitConfiguration() + { AddCvarConf(LauncherConVar.ILSpyUrl); AddCvarConf(LauncherConVar.Hub); AddCvarConf(LauncherConVar.AuthServers); @@ -55,10 +90,15 @@ public partial class ConfigurationViewModel : ViewModelBase AddCvarConf(CurrentConVar.RobustAssemblyName); AddCvarConf(CurrentConVar.ManifestDownloadProtocolVersion); } + + protected override void InitialiseInDesignMode() + { + InitConfiguration(); + } protected override void Initialise() { - InitialiseInDesignMode(); + InitConfiguration(); } } @@ -103,31 +143,34 @@ public static class ConfigControlHelper{ } } -public sealed class ComplexUnitConfigControl : StackPanel, IConfigControl +public sealed class ComplexUnitConfigControl : Border, IConfigControl { - private List<(PropertyInfo, IConfigControl)> _units = []; + private readonly List<(PropertyInfo, IConfigControl)> _units = []; private Type _objectType = typeof(object); + + private readonly StackPanel _panel = new(); public string ConfigName { get; } public bool Dirty => _units.Any(dirty => dirty.Item2.Dirty); public ComplexUnitConfigControl(string name, object obj) { - Orientation = Orientation.Vertical; - Margin = new Thickness(5); - Spacing = 2f; + Classes.Add("ConfigBorder"); + _panel.Orientation = Orientation.Vertical; + _panel.Spacing = 4f; ConfigName = name; + Child = _panel; SetValue(obj); } public void SetValue(object value) { _units.Clear(); - Children.Clear(); + _panel.Children.Clear(); _objectType = value.GetType(); - Children.Add(new Label() + _panel.Children.Add(new Label() { Content = ConfigName }); @@ -141,7 +184,8 @@ public sealed class ComplexUnitConfigControl : StackPanel, IConfigControl var control = ConfigControlHelper.GetConfigControl(propInfo.Name, propValue); - Children.Add(control as Control); + ((Control)control).Margin = new Thickness(5); + _panel.Children.Add((Control)control); _units.Add((propInfo,control)); } } @@ -158,48 +202,51 @@ public sealed class ComplexUnitConfigControl : StackPanel, IConfigControl } } -public sealed class ArrayUnitConfigControl : StackPanel, IConfigControl +public sealed class ArrayUnitConfigControl : Border, IConfigControl { private readonly List _itemControls = []; private readonly StackPanel _itemsPanel = new StackPanel() { Orientation = Orientation.Vertical }; - private readonly Button _addButton = new Button() { Content = "Add Item" }; - private int oldCount; + private readonly Button _addButton = new Button() { Content = "Add Item", Classes = { "ConfigBorder" }}; + private readonly int _oldCount; private readonly Type _elementType; + private readonly StackPanel _panel = new(); public string ConfigName { get; } - public bool Dirty => _itemControls.Any(dirty => dirty.Dirty) || _itemControls.Count != oldCount; + public bool Dirty => _itemControls.Any(dirty => dirty.Dirty) || _itemControls.Count != _oldCount; public ArrayUnitConfigControl(string name, object value) { - _elementType = value.GetType().GetElementType(); + Classes.Add("ConfigBorder"); + _elementType = value.GetType().GetElementType()!; ConfigName = name; - Orientation = Orientation.Vertical; - Margin = new Thickness(5); - Spacing = 2f; + _panel.Orientation = Orientation.Vertical; + _panel.Spacing = 4f; + _itemsPanel.Spacing = 4f; - Children.Add(new Label { Content = name }); - Children.Add(_itemsPanel); - Children.Add(_addButton); - - _addButton.Click += (_, _) => AddItem(ConfigControlHelper.CreateDefaultValue(_elementType)); + _panel.Children.Add(new Label { Content = name }); + _panel.Children.Add(_itemsPanel); + _panel.Children.Add(_addButton); + _addButton.Click += (_, _) => AddItem(ConfigControlHelper.CreateDefaultValue(_elementType)!); + Child = _panel; SetValue(value); - oldCount = _itemControls.Count; + _oldCount = _itemControls.Count; } private void AddItem(object value) { var itemPanel = new StackPanel { Orientation = Orientation.Horizontal, Spacing = 2 }; var control = ConfigControlHelper.GetConfigControl(_itemControls.Count.ToString(), value); - var removeButton = new Button { Content = "Remove" }; + var removeButton = new Button { Content = "Remove", Classes = { "ConfigBorder" }}; removeButton.Click += (_, _) => { _itemControls.Remove(control); _itemsPanel.Children.Remove(itemPanel); }; - + + ((Control)control).Margin = new Thickness(5); itemPanel.Children.Add((Control)control); itemPanel.Children.Add(removeButton); @@ -242,37 +289,35 @@ public sealed class ArrayUnitConfigControl : StackPanel, IConfigControl } } -public abstract class UnitConfigControl : StackPanel, IConfigControl where T : notnull +public abstract class UnitConfigControl : Border, IConfigControl where T : notnull { - private readonly Label _nameLabel = new Label(); - private readonly TextBox _valueLabel = new TextBox(); + private readonly Label _nameLabel = new(); + private readonly TextBox _valueLabel = new(); private string _originalValue; - - public string ConfigName { get; private set;} - - public bool Dirty - { - get - { - return _originalValue != ConfigValue; - } - } - protected string? ConfigValue + private StackPanel _panel = new(); + + public string ConfigName { get; } + + public bool Dirty => _originalValue != ConfigValue; + + protected string ConfigValue { - get => _valueLabel.Text; + get => _valueLabel.Text ?? string.Empty; set => _valueLabel.Text = value; } public UnitConfigControl(string name, T value) { + Classes.Add("ConfigBorder"); ConfigName = name; - Orientation = Orientation.Horizontal; - Children.Add(_nameLabel); - Children.Add(_valueLabel); + _panel.Orientation = Orientation.Horizontal; + _panel.Children.Add(_nameLabel); + _panel.Children.Add(_valueLabel); _nameLabel.Content = name; _nameLabel.VerticalAlignment = VerticalAlignment.Center; + Child = _panel; SetConfValue(value); _originalValue = ConfigValue; @@ -302,7 +347,7 @@ public sealed class StringUnitConfigControl(string name, string value) : UnitCon public override string GetConfValue() { - return ConfigValue ?? string.Empty; + return ConfigValue; } } @@ -315,8 +360,6 @@ public sealed class IntUnitConfigControl(string name, int value) : UnitConfigCon public override int GetConfValue() { - Debug.Assert(ConfigValue != null, nameof(ConfigValue) + " != null"); - return int.Parse(ConfigValue); } } @@ -333,8 +376,6 @@ public sealed class FloatUnitConfigControl(string name, float value) : UnitConfi public override float GetConfValue() { - Debug.Assert(ConfigValue != null, nameof(ConfigValue) + " != null"); - return float.Parse(ConfigValue, CultureInfo); } } diff --git a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs index 473fbb9..b165173 100644 --- a/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ContentBrowserViewModel.cs @@ -59,12 +59,7 @@ public sealed partial class ContentBrowserViewModel : ViewModelBase, IContentHol PopupService.Popup(loading); Task.Run(() => ContentService.Unpack(serverEntry.FileApi, myTempDir, loading)); - var startInfo = new ProcessStartInfo(){ - FileName = "explorer.exe", - Arguments = tmpDir, - }; - - Process.Start(startInfo); + ExplorerHelper.OpenFolder(tmpDir); } public void OnGoEnter() diff --git a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs index 19ffd10..de040e0 100644 --- a/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ServerOverviewModel.cs @@ -57,9 +57,15 @@ public partial class ServerOverviewModel : ViewModelBase //real think protected override void Initialise() + { + ConfigurationService.SubscribeVarChanged(LauncherConVar.Hub, OnHubListChanged, true); + } + + private void OnHubListChanged(ServerHubRecord[]? value) { var tempItems = new List(); - foreach (var record in ConfigurationService.GetConfigValue(LauncherConVar.Hub) ?? []) + + foreach (var record in value ?? []) { tempItems.Add(new ServerListTabTemplate(ServiceProvider.GetService()!.With(record.MainUrl), record.Name)); } @@ -125,17 +131,32 @@ public partial class ServerOverviewModel : ViewModelBase public class ServerViewContainer { private readonly ViewHelperService _viewHelperService; + private List favorites = []; public ServerViewContainer() { _viewHelperService = new ViewHelperService(); } - public ServerViewContainer(ViewHelperService viewHelperService) + public ServerViewContainer(ViewHelperService viewHelperService, ConfigurationService configurationService) { _viewHelperService = viewHelperService; + configurationService.SubscribeVarChanged(LauncherConVar.Favorites, OnFavoritesChange, true); } - + + private void OnFavoritesChange(string[]? value) + { + favorites = new List(value ?? []); + + foreach (var favorite in favorites) + { + if (_entries.TryGetValue(favorite, out var entry)) + { + entry.IsFavorite = true; + } + } + } + private readonly Dictionary _entries = new(); public ICollection Items => _entries.Values; @@ -158,6 +179,8 @@ public class ServerViewContainer entry = _viewHelperService.GetViewModel().WithData(url, serverStatus); + if(favorites.Contains(url.ToString())) entry.IsFavorite = true; + _entries.Add(url.ToString(), entry); } diff --git a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs index 4d9accc..b9301d6 100644 --- a/Nebula.Launcher/ViewModels/ServerEntryModelView.cs +++ b/Nebula.Launcher/ViewModels/ServerEntryModelView.cs @@ -134,17 +134,10 @@ public partial class ServerEntryModelView : ViewModelBase, IFilterConsumer SetStatus(serverStatus!); else FetchStatus(); - - IsFavorite = GetFavoriteEntries().Contains(Address.ToString()); return this; } - - private List GetFavoriteEntries() - { - return ConfigurationService.GetConfigValue(LauncherConVar.Favorites)?.ToList() ?? []; - } - + private async void FetchStatus() { try diff --git a/Nebula.Launcher/Views/Pages/ConfigurationView.axaml b/Nebula.Launcher/Views/Pages/ConfigurationView.axaml index 273f98a..fbd30b8 100644 --- a/Nebula.Launcher/Views/Pages/ConfigurationView.axaml +++ b/Nebula.Launcher/Views/Pages/ConfigurationView.axaml @@ -21,14 +21,45 @@ - + + + + + + + diff --git a/Nebula.Shared/Services/ConfigurationService.cs b/Nebula.Shared/Services/ConfigurationService.cs index 7d8c52b..013c088 100644 --- a/Nebula.Shared/Services/ConfigurationService.cs +++ b/Nebula.Shared/Services/ConfigurationService.cs @@ -8,6 +8,8 @@ namespace Nebula.Shared.Services; public class ConVar { + internal ConfigurationService.OnConfigurationChangedDelegate? OnValueChanged; + public ConVar(string name, T? defaultValue = default) { Name = name ?? throw new ArgumentNullException(nameof(name)); @@ -45,11 +47,16 @@ public class ConfigurationService ConfigurationApi = fileService.CreateFileApi("config"); } - private void SubscribeVarChanged(ConVar convar, OnConfigurationChangedDelegate @delegate) + public ConfigChangeSubscriberDisposable SubscribeVarChanged(ConVar convar, OnConfigurationChangedDelegate @delegate, bool invokeNow = false) { + convar.OnValueChanged += @delegate; + if (invokeNow) + { + @delegate(GetConfigValue(convar)); + } + return new ConfigChangeSubscriberDisposable(convar, @delegate); } - public T? GetConfigValue(ConVar conVar) { @@ -107,8 +114,12 @@ public class ConfigurationService public void SetConfigValue(ConVar conVar, T value) { - ArgumentNullException.ThrowIfNull(conVar); - if (value == null) throw new ArgumentNullException(nameof(value)); + if (value == null) + { + ConfigurationApi.Remove(GetFileName(conVar)); + conVar.OnValueChanged?.Invoke(conVar.DefaultValue); + return; + } if (!conVar.Type.IsInstanceOfType(value)) { @@ -129,6 +140,7 @@ public class ConfigurationService stream.Seek(0, SeekOrigin.Begin); ConfigurationApi.Save(GetFileName(conVar), stream); + conVar.OnValueChanged?.Invoke(value); } catch (Exception e) { @@ -140,4 +152,20 @@ public class ConfigurationService { return $"{conVar.Name}.json"; } +} + +public sealed class ConfigChangeSubscriberDisposable : IDisposable +{ + private readonly ConVar _convar; + private readonly ConfigurationService.OnConfigurationChangedDelegate _delegate; + + public ConfigChangeSubscriberDisposable(ConVar convar, ConfigurationService.OnConfigurationChangedDelegate @delegate) + { + _convar = convar; + _delegate = @delegate; + } + public void Dispose() + { + _convar.OnValueChanged -= _delegate; + } } \ No newline at end of file diff --git a/Nebula.Shared/Services/DebugService.cs b/Nebula.Shared/Services/DebugService.cs index aab8c79..8256924 100644 --- a/Nebula.Shared/Services/DebugService.cs +++ b/Nebula.Shared/Services/DebugService.cs @@ -72,17 +72,19 @@ internal class ServiceLogger : ILogger if (!DebugService.DoFileLog) return; if(!Directory.Exists(directory)) Directory.CreateDirectory(directory); + + _path = Path.Combine(directory, $"{Category}.log"); - _fileStream = File.Open(Path.Combine(directory,$"{Category}.log"), FileMode.Create, FileAccess.Write, FileShare.Read); - _streamWriter = new StreamWriter(_fileStream); + File.Create(_path).Dispose(); } public string Category { get; init; } private Dictionary Childs { get; init; } = new(); - private readonly FileStream _fileStream; - private readonly StreamWriter _streamWriter; + private FileStream? _fileStream; + private StreamWriter? _streamWriter; + private readonly string _path; public ServiceLogger GetLogger(string category) { @@ -109,17 +111,25 @@ internal class ServiceLogger : ILogger private void LogToFile(string output) { if(!DebugService.DoFileLog) return; + _fileStream = File.Open(_path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + _streamWriter = new StreamWriter(_fileStream); Root?.LogToFile(output); _streamWriter.WriteLine(output); _streamWriter.Flush(); + + _streamWriter.Dispose(); + _fileStream.Dispose(); + + _fileStream = null; + _streamWriter = null; } public void Dispose() { if (!DebugService.DoFileLog) return; - _streamWriter.Dispose(); - _fileStream.Dispose(); + _streamWriter?.Dispose(); + _fileStream?.Dispose(); foreach (var (_, child) in Childs) { child.Dispose(); diff --git a/Nebula.sln.DotSettings.user b/Nebula.sln.DotSettings.user index 4b0e9f0..c0071bb 100644 --- a/Nebula.sln.DotSettings.user +++ b/Nebula.sln.DotSettings.user @@ -1,6 +1,8 @@  + ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -9,6 +11,8 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -31,6 +35,9 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded INFO \ No newline at end of file