Improve EditServerConfigurationViewModel mostly cleanup by using ReactiveUI CodeGeneration.

This commit is contained in:
eelke 2025-10-26 11:45:17 +01:00
parent 4ff9b78db8
commit 114542b317
4 changed files with 127 additions and 73 deletions

View file

@ -1,35 +1,18 @@
using System; using System.Reactive;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq; using System.Reactive.Linq;
using Npgsql;
using pgLabII.Model; using pgLabII.Model;
using pgLabII.PgUtils.ConnectionStrings; using pgLabII.PgUtils.ConnectionStrings;
using pgLabII.Services; using pgLabII.Services;
using ReactiveUI; using ReactiveUI;
using ReactiveUI.SourceGenerators;
namespace pgLabII.ViewModels; namespace pgLabII.ViewModels;
public class EditServerConfigurationViewModel : ViewModelBase public partial class EditServerConfigurationViewModel : ViewModelBase
{ {
// Prefer new UI VM; keep old model for compatibility by wrapping when needed private readonly IConnectionStringService _service;
public ServerConfigurationViewModel Configuration { get; set; } // Store original for reverting changes
private ServerConfigurationViewModel? _originalConfiguration;
// Connection string IO
private string _inputConnectionString = string.Empty;
public string InputConnectionString
{
get => _inputConnectionString;
set
{
this.RaiseAndSetIfChanged(ref _inputConnectionString, value);
// auto-detect when input changes and we are in Auto mode
if (ForcedFormat == ForcedFormatOption.Auto)
{
DetectFormat();
}
}
}
public enum ForcedFormatOption public enum ForcedFormatOption
{ {
@ -40,50 +23,27 @@ public class EditServerConfigurationViewModel : ViewModelBase
Jdbc Jdbc
} }
private ForcedFormatOption _forcedFormat = ForcedFormatOption.Auto; public Interaction<bool, bool?> CloseInteraction { get; } = new();
public ForcedFormatOption ForcedFormat // Prefer new UI VM; keep old model for compatibility by wrapping when needed
{ public ServerConfigurationViewModel Configuration { get; set; }
get => _forcedFormat;
set
{
this.RaiseAndSetIfChanged(ref _forcedFormat, value);
// When forcing off Auto, clear detected label; when switching to Auto, re-detect
if (value == ForcedFormatOption.Auto)
DetectFormat();
else
DetectedFormat = null;
}
}
private ConnStringFormat? _detectedFormat; // Connection string IO
public ConnStringFormat? DetectedFormat [Reactive] public partial string InputConnectionString { get; set; }
{
get => _detectedFormat;
private set => this.RaiseAndSetIfChanged(ref _detectedFormat, value);
}
private ConnStringFormat _outputFormat = ConnStringFormat.Url; [Reactive] public partial ForcedFormatOption ForcedFormat { get; set; }
public ConnStringFormat OutputFormat
{
get => _outputFormat;
set => this.RaiseAndSetIfChanged(ref _outputFormat, value);
}
private string _outputConnectionString = string.Empty; [ObservableAsProperty] private ConnStringFormat? _detectedFormat;
public string OutputConnectionString [Reactive] public partial ConnStringFormat OutputFormat { get; set; }
{
get => _outputConnectionString; [Reactive] public partial string OutputConnectionString { get; set; }
set => this.RaiseAndSetIfChanged(ref _outputConnectionString, value);
}
public ReactiveCommand<Unit, Unit> ParseConnectionStringCommand { get; } public ReactiveCommand<Unit, Unit> ParseConnectionStringCommand { get; }
public ReactiveCommand<Unit, Unit> GenerateConnectionStringCommand { get; } public ReactiveCommand<Unit, Unit> GenerateConnectionStringCommand { get; }
public ReactiveCommand<Unit, Unit> CopyOutputConnectionStringCommand { get; } public ReactiveCommand<Unit, Unit> CopyOutputConnectionStringCommand { get; }
public ReactiveCommand<Unit, Unit> SaveAndCloseCommand { get; } public ReactiveCommand<Unit, Unit> SaveCommand { get; }
public ReactiveCommand<Unit, Unit> CloseCommand { get; } public ReactiveCommand<Unit, Unit> RevertCommand { get; }
private readonly IConnectionStringService _service;
public EditServerConfigurationViewModel() public EditServerConfigurationViewModel()
{ {
@ -94,25 +54,31 @@ public class EditServerConfigurationViewModel : ViewModelBase
GenerateConnectionStringCommand = ReactiveCommand.Create(GenerateConnectionString); GenerateConnectionStringCommand = ReactiveCommand.Create(GenerateConnectionString);
CopyOutputConnectionStringCommand = ReactiveCommand.Create(() => { /* no-op placeholder */ }); CopyOutputConnectionStringCommand = ReactiveCommand.Create(() => { /* no-op placeholder */ });
SaveAndCloseCommand = ReactiveCommand.Create(() => { }); SaveCommand = ReactiveCommand.CreateFromTask(Save);
CloseCommand = ReactiveCommand.Create(() => { }); RevertCommand = ReactiveCommand.CreateFromTask(Revert);
_detectedFormatHelper = this.WhenAnyValue(x => x.ForcedFormat, x => x.InputConnectionString,
DetectFormat)
.ToProperty(this, x => x.DetectedFormat);
} }
public EditServerConfigurationViewModel(ServerConfigurationViewModel configuration) public EditServerConfigurationViewModel(ServerConfigurationViewModel configuration)
: this() : this()
{ {
Configuration = configuration; Configuration = configuration;
InitializeFromCopy();
} }
private void DetectFormat() private ConnStringFormat? DetectFormat(ForcedFormatOption format, string input)
{ {
if (string.IsNullOrWhiteSpace(InputConnectionString)) if (format != ForcedFormatOption.Auto)
{ return null;
DetectedFormat = null;
return; if (string.IsNullOrWhiteSpace(input))
} return null;
var res = _service.DetectFormat(InputConnectionString); var res = _service.DetectFormat(InputConnectionString);
DetectedFormat = res.IsSuccess ? res.Value : null; return res.IsSuccess ? res.Value : null;
} }
private void ParseConnectionString() private void ParseConnectionString()
@ -173,4 +139,73 @@ public class EditServerConfigurationViewModel : ViewModelBase
if (r.IsSuccess) if (r.IsSuccess)
OutputConnectionString = r.Value; OutputConnectionString = r.Value;
} }
/// <summary>
/// Creates a deep copy of the configuration for editing.
/// This ensures changes are isolated until Save is called.
/// </summary>
private void InitializeFromCopy()
{
_originalConfiguration = Configuration;
// Create a copy of the entity
var copiedEntity = new ServerConfigurationEntity
{
Id = _originalConfiguration.Entity.Id,
Name = _originalConfiguration.Entity.Name,
Host = _originalConfiguration.Entity.Host,
Port = _originalConfiguration.Entity.Port,
InitialDatabase = _originalConfiguration.Entity.InitialDatabase,
SslMode = _originalConfiguration.Entity.SslMode,
ColorEnabled = _originalConfiguration.Entity.ColorEnabled,
ColorArgb = _originalConfiguration.Entity.ColorArgb,
UserName = _originalConfiguration.Entity.UserName,
Password = _originalConfiguration.Entity.Password
};
// Create a new ViewModel wrapping the copied entity
Configuration = new ServerConfigurationViewModel(copiedEntity);
}
/// <summary>
/// Copies changes from the edited configuration back to the original.
/// </summary>
private async Task Save()
{
if (_originalConfiguration == null) return;
// Copy all properties from the edited configuration back to the original
_originalConfiguration.Name = Configuration.Name;
_originalConfiguration.Host = Configuration.Host;
_originalConfiguration.Port = Configuration.Port;
_originalConfiguration.InitialDatabase = Configuration.InitialDatabase;
_originalConfiguration.DefaultSslMode = Configuration.DefaultSslMode;
_originalConfiguration.ColorEnabled = Configuration.ColorEnabled;
_originalConfiguration.Color = Configuration.Color;
_originalConfiguration.UserName = Configuration.UserName;
_originalConfiguration.Password = Configuration.Password;
await CloseInteraction.Handle(true);
}
/// <summary>
/// Reverts to the original configuration, discarding all changes.
/// </summary>
private async Task Revert()
{
if (_originalConfiguration == null) return;
// Copy all properties from the original back to the working configuration
Configuration.Name = _originalConfiguration.Name;
Configuration.Host = _originalConfiguration.Host;
Configuration.Port = _originalConfiguration.Port;
Configuration.InitialDatabase = _originalConfiguration.InitialDatabase;
Configuration.DefaultSslMode = _originalConfiguration.DefaultSslMode;
Configuration.ColorEnabled = _originalConfiguration.ColorEnabled;
Configuration.Color = _originalConfiguration.Color;
Configuration.UserName = _originalConfiguration.UserName;
Configuration.Password = _originalConfiguration.Password;
await CloseInteraction.Handle(false);
}
} }

View file

@ -12,7 +12,7 @@
<vm:EditServerConfigurationViewModel /> <vm:EditServerConfigurationViewModel />
</Design.DataContext> </Design.DataContext>
<Grid Margin="12" RowDefinitions="Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="*"> <Grid Margin="12" RowDefinitions="Auto,Auto,Auto,Auto" ColumnDefinitions="*">
<!-- Basic Details --> <!-- Basic Details -->
<StackPanel Grid.Row="0" Spacing="6"> <StackPanel Grid.Row="0" Spacing="6">
<TextBlock FontWeight="Bold" Text="Details" Margin="0,0,0,4"/> <TextBlock FontWeight="Bold" Text="Details" Margin="0,0,0,4"/>
@ -93,6 +93,10 @@
<TextBox TextWrapping="Wrap" AcceptsReturn="True" MinHeight="60" IsReadOnly="True" Text="{Binding OutputConnectionString}"/> <TextBox TextWrapping="Wrap" AcceptsReturn="True" MinHeight="60" IsReadOnly="True" Text="{Binding OutputConnectionString}"/>
</StackPanel> </StackPanel>
<!-- Spacer / Future buttons row could go here --> <!-- Buttons Row -->
<StackPanel Grid.Row="3" Orientation="Horizontal" Spacing="8" Margin="0,12,0,0" HorizontalAlignment="Right">
<Button Content="Revert" Command="{Binding RevertCommand}" MinWidth="80"/>
<Button Content="Save" Command="{Binding SaveCommand}" MinWidth="80"/>
</StackPanel>
</Grid> </Grid>
</Window> </Window>

View file

@ -1,24 +1,38 @@
using Avalonia; using System.Reactive.Disposables;
using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Avalonia.ReactiveUI;
using pgLabII.ViewModels; using pgLabII.ViewModels;
using ReactiveUI;
namespace pgLabII.Views; namespace pgLabII.Views;
public partial class EditServerConfigurationWindow : Window public partial class EditServerConfigurationWindow : ReactiveWindow<EditServerConfigurationWindow>
{ {
private readonly EditServerConfigurationViewModel _viewModel;
public EditServerConfigurationWindow() public EditServerConfigurationWindow()
: this(null) : this(null)
{ {
} }
public EditServerConfigurationWindow(EditServerConfigurationViewModel? viewModel) public EditServerConfigurationWindow(EditServerConfigurationViewModel? viewModel)
{ {
InitializeComponent(); InitializeComponent();
DataContext = viewModel ?? new EditServerConfigurationViewModel( DataContext = _viewModel = viewModel ?? new EditServerConfigurationViewModel(
new(new())); new(new()));
this.WhenActivated(disposables =>
{
// Subscribe to the CloseInteraction
_viewModel!.CloseInteraction.RegisterHandler(interaction =>
{
//DialogResult = interaction.Input; // true/false/null
Close();
interaction.SetOutput(true);
}).DisposeWith(disposables);
});
} }
public bool New { get; set; } public bool New { get; set; }

View file

@ -3,6 +3,7 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault> <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
</PropertyGroup> </PropertyGroup>