diff --git a/Nebula.Launcher/Configurations/ArrayUnitConfigControl.cs b/Nebula.Launcher/Configurations/ArrayUnitConfigControl.cs new file mode 100644 index 0000000..d339d61 --- /dev/null +++ b/Nebula.Launcher/Configurations/ArrayUnitConfigControl.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Layout; + +namespace Nebula.Launcher.ViewModels.Pages; + +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 = new Label() + { + 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 ArrayUnitConfigControl(string name, object value) + { + Classes.Add("ConfigBorder"); + _elementType = value.GetType().GetElementType()!; + + ConfigName = name; + _panel.Orientation = Orientation.Vertical; + _panel.Spacing = 4f; + _itemsPanel.Spacing = 4f; + + _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; + } + + 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 = new Label(){ 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); + + _itemsPanel.Children.Add(itemPanel); + _itemControls.Add(control); + } + + public void SetValue(object value) + { + _itemControls.Clear(); + _itemsPanel.Children.Clear(); + + if (value is IEnumerable list) + { + foreach (var item in list) + { + AddItem(item); + } + } + } + + public object GetValue() + { + return ConvertArray(_itemControls.Select(c => c.GetValue()).ToArray(), _elementType); + } + + public static Array ConvertArray(Array sourceArray, Type targetType) + { + int length = sourceArray.Length; + var newArray = Array.CreateInstance(targetType, length); + + for (int i = 0; i < length; i++) + { + var value = sourceArray.GetValue(i); + var converted = Convert.ChangeType(value, targetType); + newArray.SetValue(converted, i); + } + + return newArray; + } +} \ No newline at end of file diff --git a/Nebula.Shared/Configurations/ComplexConVarBinder.cs b/Nebula.Launcher/Configurations/ComplexConVarBinder.cs similarity index 78% rename from Nebula.Shared/Configurations/ComplexConVarBinder.cs rename to Nebula.Launcher/Configurations/ComplexConVarBinder.cs index 4711488..b3b6e36 100644 --- a/Nebula.Shared/Configurations/ComplexConVarBinder.cs +++ b/Nebula.Launcher/Configurations/ComplexConVarBinder.cs @@ -1,7 +1,10 @@ +using System; using System.ComponentModel; -using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Nebula.Shared.Configurations; -namespace Nebula.Shared.Configurations; +namespace Nebula.Launcher.Configurations; public abstract class ComplexConVarBinder : INotifyPropertyChanged, INotifyPropertyChanging { @@ -23,6 +26,17 @@ public abstract class ComplexConVarBinder : INotifyPropertyChanged, INotifyPr _ = SetValueAsync(value); } } + + public bool HasValue + { + get + { + lock (_lock) + { + return _baseConVar.HasValue; + } + } + } protected ComplexConVarBinder(ConVarObserver baseConVar) { @@ -55,11 +69,13 @@ public abstract class ComplexConVarBinder : INotifyPropertyChanged, INotifyPr private void BaseConVarOnPropertyChanged(object? sender, PropertyChangedEventArgs e) { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasValue))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value))); } private void BaseConVarOnPropertyChanging(object? sender, PropertyChangingEventArgs e) { + PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(HasValue))); PropertyChanging?.Invoke(this, new PropertyChangingEventArgs(nameof(Value))); } diff --git a/Nebula.Launcher/Configurations/ComplexUnitConfigControl.cs b/Nebula.Launcher/Configurations/ComplexUnitConfigControl.cs new file mode 100644 index 0000000..bf8c4ba --- /dev/null +++ b/Nebula.Launcher/Configurations/ComplexUnitConfigControl.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Layout; + +namespace Nebula.Launcher.ViewModels.Pages; + +public sealed class ComplexUnitConfigControl : Border, IConfigControl +{ + 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) + { + Classes.Add("ConfigBorder"); + _panel.Orientation = Orientation.Vertical; + _panel.Spacing = 4f; + ConfigName = name; + Child = _panel; + SetValue(obj); + } + + public void SetValue(object value) + { + _units.Clear(); + _panel.Children.Clear(); + _objectType = value.GetType(); + + _panel.Children.Add(new Label() + { + Content = ConfigName + }); + + foreach (var propInfo in _objectType.GetProperties()) + { + if(propInfo.PropertyType.IsInterface) + continue; + + var propValue = propInfo.GetValue(value); + + var control = ConfigControlHelper.GetConfigControl(propInfo.Name, propValue!); + + ((Control)control).Margin = new Thickness(5); + _panel.Children.Add((Control)control); + _units.Add((propInfo,control)); + } + } + + public object GetValue() + { + var obj = ConfigControlHelper.CreateDefaultValue(_objectType); + foreach (var (fieldInfo, configControl) in _units) + { + fieldInfo.SetValue(obj, configControl.GetValue()); + } + + return obj!; + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Configurations/ConfigControlHelper.cs b/Nebula.Launcher/Configurations/ConfigControlHelper.cs new file mode 100644 index 0000000..a7304eb --- /dev/null +++ b/Nebula.Launcher/Configurations/ConfigControlHelper.cs @@ -0,0 +1,39 @@ +using System; +using System.Linq; + +namespace Nebula.Launcher.ViewModels.Pages; + +public static class ConfigControlHelper{ + public static IConfigControl GetConfigControl(string name,object value) + { + switch (value) + { + case string stringValue: + return new StringUnitConfigControl(name, stringValue); + case int intValue: + return new IntUnitConfigControl(name, intValue); + case float floatValue: + return new FloatUnitConfigControl(name, floatValue); + } + + var valueType = value.GetType(); + + if (valueType.IsArray) + return new ArrayUnitConfigControl(name, value); + + return new ComplexUnitConfigControl(name, value); + } + + public static object? CreateDefaultValue(Type type) + { + if(type.IsValueType) + return Activator.CreateInstance(type); + + var ctor = type.GetConstructors().First(); + var parameters = ctor.GetParameters() + .Select(p => CreateDefaultValue(p.ParameterType)) + .ToArray(); + + return ctor.Invoke(parameters); + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Configurations/FloatUnitConfigControl.cs b/Nebula.Launcher/Configurations/FloatUnitConfigControl.cs new file mode 100644 index 0000000..1fefc67 --- /dev/null +++ b/Nebula.Launcher/Configurations/FloatUnitConfigControl.cs @@ -0,0 +1,19 @@ +using System.Globalization; + +namespace Nebula.Launcher.ViewModels.Pages; + +public sealed class FloatUnitConfigControl(string name, float value) : UnitConfigControl(name, value) +{ + + public CultureInfo CultureInfo = CultureInfo.InvariantCulture; + + public override void SetConfValue(float value) + { + ConfigValue = value.ToString(CultureInfo); + } + + public override float GetConfValue() + { + return float.Parse(ConfigValue, CultureInfo); + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Configurations/IConfigControl.cs b/Nebula.Launcher/Configurations/IConfigControl.cs new file mode 100644 index 0000000..71aff94 --- /dev/null +++ b/Nebula.Launcher/Configurations/IConfigControl.cs @@ -0,0 +1,9 @@ +namespace Nebula.Launcher.ViewModels.Pages; + +public interface IConfigControl +{ + public string ConfigName { get; } + public bool Dirty {get;} + public abstract void SetValue(object value); + public abstract object GetValue(); +} \ No newline at end of file diff --git a/Nebula.Launcher/Configurations/IntUnitConfigControl.cs b/Nebula.Launcher/Configurations/IntUnitConfigControl.cs new file mode 100644 index 0000000..fbe91fc --- /dev/null +++ b/Nebula.Launcher/Configurations/IntUnitConfigControl.cs @@ -0,0 +1,14 @@ +namespace Nebula.Launcher.ViewModels.Pages; + +public sealed class IntUnitConfigControl(string name, int value) : UnitConfigControl(name, value) +{ + public override void SetConfValue(int value) + { + ConfigValue = value.ToString(); + } + + public override int GetConfValue() + { + return int.Parse(ConfigValue); + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Configurations/StringUnitConfigControl.cs b/Nebula.Launcher/Configurations/StringUnitConfigControl.cs new file mode 100644 index 0000000..fe3bb20 --- /dev/null +++ b/Nebula.Launcher/Configurations/StringUnitConfigControl.cs @@ -0,0 +1,14 @@ +namespace Nebula.Launcher.ViewModels.Pages; + +public sealed class StringUnitConfigControl(string name, string value) : UnitConfigControl(name, value) +{ + public override void SetConfValue(string value) + { + ConfigValue = value; + } + + public override string GetConfValue() + { + return ConfigValue; + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Configurations/UnitConfigControl.cs b/Nebula.Launcher/Configurations/UnitConfigControl.cs new file mode 100644 index 0000000..6464b17 --- /dev/null +++ b/Nebula.Launcher/Configurations/UnitConfigControl.cs @@ -0,0 +1,53 @@ +using Avalonia.Controls; +using Avalonia.Layout; + +namespace Nebula.Launcher.ViewModels.Pages; + +public abstract class UnitConfigControl : Border, IConfigControl where T : notnull +{ + private readonly Label _nameLabel = new(); + private readonly TextBox _valueLabel = new(); + private string _originalValue; + + private StackPanel _panel = new(); + + public string ConfigName { get; } + + public bool Dirty => _originalValue != ConfigValue; + + protected string ConfigValue + { + get => _valueLabel.Text ?? string.Empty; + set => _valueLabel.Text = value; + } + + public UnitConfigControl(string name, T value) + { + Classes.Add("ConfigBorder"); + ConfigName = name; + _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; + } + + public abstract void SetConfValue(T value); + + public abstract T GetConfValue(); + + public void SetValue(object value) + { + SetConfValue((T)value); + } + + public object GetValue() + { + return GetConfValue()!; + } +} \ No newline at end of file diff --git a/Nebula.Launcher/Services/LocalisationService.cs b/Nebula.Launcher/Services/LocalisationService.cs index 36e32b4..5988d25 100644 --- a/Nebula.Launcher/Services/LocalisationService.cs +++ b/Nebula.Launcher/Services/LocalisationService.cs @@ -68,12 +68,16 @@ public partial class LocalisationService public class LocaledText : MarkupExtension { public string Key { get; set; } + public Dictionary? Options { get; set; } public LocaledText(string key) => Key = key; + public LocaledText() + { + } + public override object ProvideValue(IServiceProvider serviceProvider) { - // Fetch the localized string using the key - return LocalisationService.GetString(Key); + return LocalisationService.GetString(Key, Options); } } \ No newline at end of file diff --git a/Nebula.Launcher/ViewModels/MainViewModel.cs b/Nebula.Launcher/ViewModels/MainViewModel.cs index c61c72b..c100608 100644 --- a/Nebula.Launcher/ViewModels/MainViewModel.cs +++ b/Nebula.Launcher/ViewModels/MainViewModel.cs @@ -40,16 +40,8 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private bool _isPopupClosable = true; [ObservableProperty] private bool _popup; [ObservableProperty] private ListItemTemplate? _selectedListItem; - - public bool IsLoggedIn => AccountInfoViewModel.Credentials.Value is not null; + [ObservableProperty] private string? _loginText = LocalisationService.GetString("auth-current-login-no-name"); - public string LoginText => LocalisationService.GetString("auth-current-login-name", - new Dictionary - { - { "login", AccountInfoViewModel.Credentials.Value?.Login ?? "" }, - { "auth_server", AccountInfoViewModel.CurrentAuthServerName} - }); - [GenerateProperty] private LocalisationService LocalisationService { get; } // Не убирать! Без этой хуйни вся локализация идет в пизду! [GenerateProperty] private AccountInfoViewModel AccountInfoViewModel { get; } [GenerateProperty] private DebugService DebugService { get; } = default!; @@ -74,15 +66,29 @@ public partial class MainViewModel : ViewModelBase protected override void Initialise() { - AccountInfoViewModel.PropertyChanged += (sender, args) => + AccountInfoViewModel.Credentials.PropertyChanged += (_, args) => { - if (args.PropertyName != nameof(AccountInfoViewModel.Credentials)) - return; + if (args.PropertyName is not nameof(AccountInfoViewModel.Credentials.Value)) return; - OnPropertyChanged(nameof(LoginText)); - OnPropertyChanged(nameof(IsLoggedIn)); + if(AccountInfoViewModel.Credentials.HasValue) + { + LoginText = + LocalisationService.GetString("auth-current-login-name", + new Dictionary + { + { "login", AccountInfoViewModel.Credentials.Value?.Login ?? "" }, + { + "auth_server", + AccountInfoViewModel.GetServerAuthName(AccountInfoViewModel.Credentials.Value) ?? "" + } + }); + } + else + { + LoginText = LocalisationService.GetString("auth-current-login-no-name"); + } }; - + _logger = DebugService.GetLogger(this); using var stream = typeof(MainViewModel).Assembly diff --git a/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs b/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs index 7eb8e75..fee8a5a 100644 --- a/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/AccountInfoViewModel.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net.Http; using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; +using Nebula.Launcher.Configurations; using Nebula.Launcher.Models.Auth; using Nebula.Launcher.Services; using Nebula.Launcher.ViewModels.Popup; @@ -41,9 +42,8 @@ public partial class AccountInfoViewModel : ViewModelBase public ObservableCollection Accounts { get; } = new(); public ObservableCollection AuthUrls { get; } = new(); - public string CurrentAuthServerName => GetServerAuthName(Credentials.Value); - public ComplexConVarBinder Credentials; + public ComplexConVarBinder Credentials { get; private set; } private ILogger _logger; @@ -303,8 +303,8 @@ public partial class AccountInfoViewModel : ViewModelBase { var unexpectedError = new Exception(LocalisationService.GetString("auth-error"), e); _logger.Error(unexpectedError); - PopupMessageService.Popup(unexpectedError); - return null; + //PopupMessageService.Popup(unexpectedError); + return authTokenCredentials; } } @@ -364,7 +364,6 @@ public partial class AccountInfoViewModel : ViewModelBase { accountInfoViewModel.IsLogged = false; accountInfoViewModel._logger.Log("clearing credentials"); - accountInfoViewModel.OnPropertyChanged(nameof(CurrentAuthServerName)); return null; } @@ -408,8 +407,7 @@ public partial class AccountInfoViewModel : ViewModelBase } accountInfoViewModel.IsLogged = true; - accountInfoViewModel.OnPropertyChanged(nameof(CurrentAuthServerName)); - + return currProfile; } } diff --git a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs index 6064876..61e7ce7 100644 --- a/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs +++ b/Nebula.Launcher/ViewModels/Pages/ConfigurationViewModel.cs @@ -1,16 +1,9 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Globalization; using System.IO; using System.IO.Compression; -using System.Linq; -using System.Reflection; using System.Threading.Tasks; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Layout; using Nebula.Launcher.Services; using Nebula.Launcher.ViewModels.Popup; using Nebula.Launcher.Views.Pages; @@ -119,288 +112,4 @@ public partial class ConfigurationViewModel : ViewModelBase { InitConfiguration(); } -} - -public static class ConfigControlHelper{ - public static IConfigControl GetConfigControl(string name,object value) - { - switch (value) - { - case string stringValue: - return new StringUnitConfigControl(name, stringValue); - case int intValue: - return new IntUnitConfigControl(name, intValue); - case float floatValue: - return new FloatUnitConfigControl(name, floatValue); - } - - var valueType = value.GetType(); - - if (valueType.IsArray) - return new ArrayUnitConfigControl(name, value); - - return new ComplexUnitConfigControl(name, value); - } - - public static object? CreateDefaultValue(Type type) - { - if(type.IsValueType) - return Activator.CreateInstance(type); - - var ctor = type.GetConstructors().First(); - var parameters = ctor.GetParameters() - .Select(p => CreateDefaultValue(p.ParameterType)) - .ToArray(); - - return ctor.Invoke(parameters); - } -} - -public sealed class ComplexUnitConfigControl : Border, IConfigControl -{ - 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) - { - Classes.Add("ConfigBorder"); - _panel.Orientation = Orientation.Vertical; - _panel.Spacing = 4f; - ConfigName = name; - Child = _panel; - SetValue(obj); - } - - public void SetValue(object value) - { - _units.Clear(); - _panel.Children.Clear(); - _objectType = value.GetType(); - - _panel.Children.Add(new Label() - { - Content = ConfigName - }); - - foreach (var propInfo in _objectType.GetProperties()) - { - if(propInfo.PropertyType.IsInterface) - continue; - - var propValue = propInfo.GetValue(value); - - var control = ConfigControlHelper.GetConfigControl(propInfo.Name, propValue!); - - ((Control)control).Margin = new Thickness(5); - _panel.Children.Add((Control)control); - _units.Add((propInfo,control)); - } - } - - public object GetValue() - { - var obj = ConfigControlHelper.CreateDefaultValue(_objectType); - foreach (var (fieldInfo, configControl) in _units) - { - fieldInfo.SetValue(obj, configControl.GetValue()); - } - - return obj!; - } -} - -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 = new Label() - { - 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 ArrayUnitConfigControl(string name, object value) - { - Classes.Add("ConfigBorder"); - _elementType = value.GetType().GetElementType()!; - - ConfigName = name; - _panel.Orientation = Orientation.Vertical; - _panel.Spacing = 4f; - _itemsPanel.Spacing = 4f; - - _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; - } - - 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 = new Label(){ 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); - - _itemsPanel.Children.Add(itemPanel); - _itemControls.Add(control); - } - - public void SetValue(object value) - { - _itemControls.Clear(); - _itemsPanel.Children.Clear(); - - if (value is IEnumerable list) - { - foreach (var item in list) - { - AddItem(item); - } - } - } - - public object GetValue() - { - return ConvertArray(_itemControls.Select(c => c.GetValue()).ToArray(), _elementType); - } - - public static Array ConvertArray(Array sourceArray, Type targetType) - { - int length = sourceArray.Length; - var newArray = Array.CreateInstance(targetType, length); - - for (int i = 0; i < length; i++) - { - var value = sourceArray.GetValue(i); - var converted = Convert.ChangeType(value, targetType); - newArray.SetValue(converted, i); - } - - return newArray; - } -} - -public abstract class UnitConfigControl : Border, IConfigControl where T : notnull -{ - private readonly Label _nameLabel = new(); - private readonly TextBox _valueLabel = new(); - private string _originalValue; - - private StackPanel _panel = new(); - - public string ConfigName { get; } - - public bool Dirty => _originalValue != ConfigValue; - - protected string ConfigValue - { - get => _valueLabel.Text ?? string.Empty; - set => _valueLabel.Text = value; - } - - public UnitConfigControl(string name, T value) - { - Classes.Add("ConfigBorder"); - ConfigName = name; - _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; - } - - public abstract void SetConfValue(T value); - - public abstract T GetConfValue(); - - public void SetValue(object value) - { - SetConfValue((T)value); - } - - public object GetValue() - { - return GetConfValue()!; - } -} - -public sealed class StringUnitConfigControl(string name, string value) : UnitConfigControl(name, value) -{ - public override void SetConfValue(string value) - { - ConfigValue = value; - } - - public override string GetConfValue() - { - return ConfigValue; - } -} - -public sealed class IntUnitConfigControl(string name, int value) : UnitConfigControl(name, value) -{ - public override void SetConfValue(int value) - { - ConfigValue = value.ToString(); - } - - public override int GetConfValue() - { - return int.Parse(ConfigValue); - } -} - -public sealed class FloatUnitConfigControl(string name, float value) : UnitConfigControl(name, value) -{ - - public CultureInfo CultureInfo = CultureInfo.InvariantCulture; - - public override void SetConfValue(float value) - { - ConfigValue = value.ToString(CultureInfo); - } - - public override float GetConfValue() - { - return float.Parse(ConfigValue, CultureInfo); - } -} - - -public interface IConfigControl -{ - public string ConfigName { get; } - public bool Dirty {get;} - public abstract void SetValue(object value); - public abstract object GetValue(); } \ No newline at end of file diff --git a/Nebula.Launcher/Views/MainView.axaml b/Nebula.Launcher/Views/MainView.axaml index 4811a02..c97c5aa 100644 --- a/Nebula.Launcher/Views/MainView.axaml +++ b/Nebula.Launcher/Views/MainView.axaml @@ -133,8 +133,9 @@ Path="/Assets/svg/user.svg" Width="10" /> - - + diff --git a/Nebula.Launcher/Views/Pages/AccountInfoView.axaml b/Nebula.Launcher/Views/Pages/AccountInfoView.axaml index cdfdfee..1882b9b 100644 --- a/Nebula.Launcher/Views/Pages/AccountInfoView.axaml +++ b/Nebula.Launcher/Views/Pages/AccountInfoView.axaml @@ -191,11 +191,11 @@ - + - + diff --git a/Nebula.Shared/Configurations/ConVarObserver.cs b/Nebula.Shared/Configurations/ConVarObserver.cs index 282850f..e5f2127 100644 --- a/Nebula.Shared/Configurations/ConVarObserver.cs +++ b/Nebula.Shared/Configurations/ConVarObserver.cs @@ -12,6 +12,8 @@ public sealed class ConVarObserver : IDisposable, INotifyPropertyChanged, INo private T? _value; private ConfigurationService.OnConfigurationChangedDelegate _delegate; + public bool HasValue => Value != null; + public T? Value { get => _value; @@ -31,6 +33,7 @@ public sealed class ConVarObserver : IDisposable, INotifyPropertyChanged, INo private void OnValueChanged(T? value) { OnPropertyChanging(nameof(Value)); + OnPropertyChanging(nameof(HasValue)); if(value is null && _value is null) return; @@ -39,17 +42,13 @@ public sealed class ConVarObserver : IDisposable, INotifyPropertyChanged, INo _value = value; OnPropertyChanged(nameof(Value)); + OnPropertyChanged(nameof(HasValue)); } public void Dispose() { _convar.OnValueChanged -= OnValueChanged; } - - public bool HasValue() - { - return Value != null; - } public static implicit operator T? (ConVarObserver convar) => convar.Value; diff --git a/Nebula.Shared/Services/DebugService.cs b/Nebula.Shared/Services/DebugService.cs index 8256924..761e4f1 100644 --- a/Nebula.Shared/Services/DebugService.cs +++ b/Nebula.Shared/Services/DebugService.cs @@ -1,7 +1,6 @@ +using System.Collections.Concurrent; using System.Reflection; -using Nebula.Shared.FileApis; using Nebula.Shared.Services.Logging; -using Robust.LoaderApi; namespace Nebula.Shared.Services; @@ -9,16 +8,23 @@ namespace Nebula.Shared.Services; public class DebugService : IDisposable { public static bool DoFileLog; - private ServiceLogger Root {get; set;} private readonly string _path = Path.Combine(FileService.RootPath, "log", Assembly.GetEntryAssembly()?.GetName().Name ?? "App"); - + public DebugService() { ClearLog(); - Root = new ServiceLogger("Root",_path); - Root.GetLogger("DebugService").Log("Initializing debug service " + (DoFileLog ? "with file logging" : "without file logging")); + Root = new ServiceLogger("Root", _path); + Root.GetLogger("DebugService") + .Log("Initializing debug service " + (DoFileLog ? "with file logging" : "without file logging")); + } + + private ServiceLogger Root { get; } + + public void Dispose() + { + Root.Dispose(); } public ILogger GetLogger(string loggerName) @@ -31,25 +37,14 @@ public class DebugService : IDisposable return Root.GetLogger(objectToLog.GetType().Name); } - public void Dispose() - { - Root.Dispose(); - } - private void ClearLog() { - if(!Directory.Exists(_path)) + if (!Directory.Exists(_path)) return; var di = new DirectoryInfo(_path); - foreach (var file in di.GetFiles()) - { - file.Delete(); - } - foreach (var dir in di.GetDirectories()) - { - dir.Delete(true); - } + foreach (var file in di.GetFiles()) file.Delete(); + foreach (var dir in di.GetDirectories()) dir.Delete(true); } } @@ -63,101 +58,101 @@ public enum LoggerCategory internal class ServiceLogger : ILogger { private readonly string _directory; - public ServiceLogger? Root { get; private set; } + private readonly string _path; + public ServiceLogger(string category, string directory) { _directory = directory; Category = category; if (!DebugService.DoFileLog) return; - - if(!Directory.Exists(directory)) Directory.CreateDirectory(directory); + + if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); _path = Path.Combine(directory, $"{Category}.log"); - + File.Create(_path).Dispose(); } - public string Category { get; init; } - - private Dictionary Childs { get; init; } = new(); - - private FileStream? _fileStream; - private StreamWriter? _streamWriter; - private readonly string _path; + public ServiceLogger? Root { get; private set; } + + public string Category { get; init; } + private ConcurrentDictionary Childs { get; } = new(); - public ServiceLogger GetLogger(string category) - { - if (Childs.TryGetValue(category, out var logger)) - return logger; - - logger = new ServiceLogger(category, _directory); - logger.Root = this; - Childs.Add(category, logger); - return logger; - } - public void Log(LoggerCategory loggerCategory, string message) { - var output = DebugService.DoFileLog + var output = DebugService.DoFileLog ? $"[{DateTime.Now.ToUniversalTime():yyyy-MM-dd HH:mm:ss}][{Enum.GetName(loggerCategory)}][{Category}]: {message}" : message; - - Console.WriteLine(output); - - LogToFile(output); - } - 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; + Console.WriteLine(output); + + LogToFile(output); } public void Dispose() { if (!DebugService.DoFileLog) return; - - _streamWriter?.Dispose(); - _fileStream?.Dispose(); + foreach (var (_, child) in Childs) { child.Dispose(); } - Childs.Clear(); + + Childs.Clear(); // Not strictly necessary, but keeps intent clear + } + + public ServiceLogger GetLogger(string category) + { + return Childs.GetOrAdd(category, key => + { + var logger = new ServiceLogger(key, _directory) + { + Root = this + }; + return logger; + }); + } + + private void LogToFile(string output) + { + if (!DebugService.DoFileLog) return; + + try + { + Root?.LogToFile(output); // Log to parent first + + using var fileStream = File.Open(_path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); + using var streamWriter = new StreamWriter(fileStream); + streamWriter.WriteLine(output); + } + catch (IOException ex) + { + Console.WriteLine($"[Logging Error] Failed to write log: {ex.Message}"); + } } } public static class LoggerExtensions { - public static void Debug(this ILogger logger,string message) + public static void Debug(this ILogger logger, string message) { logger.Log(LoggerCategory.Debug, message); } - public static void Error(this ILogger logger,string message) + public static void Error(this ILogger logger, string message) { logger.Log(LoggerCategory.Error, message); } - public static void Log(this ILogger logger,string message) + public static void Log(this ILogger logger, string message) { logger.Log(LoggerCategory.Log, message); } - public static void Error(this ILogger logger,Exception e) + public static void Error(this ILogger logger, Exception e) { - Error(logger,e.Message + "\r\n" + e.StackTrace); + Error(logger, e.Message + "\r\n" + e.StackTrace); if (e.InnerException != null) Error(logger, e.InnerException); }