feat(configmanager): add DiffPreviewDialog
Add DiffPreviewDialog and DiffPreviewDialogViewModel to display a diff preview of configuration changes before saving. The dialog shows line numbers, insertions (green), deletions (red), and unchanged lines with a dark theme matching the ConfigManager design spec.
This commit is contained in:
+75
@@ -0,0 +1,75 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
|
||||
namespace JdeScoping.ConfigManager.ViewModels.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for the diff preview dialog.
|
||||
/// </summary>
|
||||
public class DiffPreviewDialogViewModel : ViewModelBase
|
||||
{
|
||||
private bool _result;
|
||||
|
||||
public DiffPreviewDialogViewModel(DiffResult diff)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(diff);
|
||||
|
||||
Lines = new ObservableCollection<DiffLineViewModel>(
|
||||
diff.Lines.Select(l => new DiffLineViewModel(l)));
|
||||
Insertions = diff.Insertions;
|
||||
Deletions = diff.Deletions;
|
||||
HasChanges = diff.HasChanges;
|
||||
|
||||
SaveCommand = new RelayCommand(() => { Result = true; RequestClose?.Invoke(); });
|
||||
CancelCommand = new RelayCommand(() => { Result = false; RequestClose?.Invoke(); });
|
||||
}
|
||||
|
||||
public ObservableCollection<DiffLineViewModel> Lines { get; }
|
||||
public int Insertions { get; }
|
||||
public int Deletions { get; }
|
||||
public bool HasChanges { get; }
|
||||
|
||||
public bool Result
|
||||
{
|
||||
get => _result;
|
||||
private set => SetProperty(ref _result, value);
|
||||
}
|
||||
|
||||
public ICommand SaveCommand { get; }
|
||||
public ICommand CancelCommand { get; }
|
||||
public Action? RequestClose { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for a single diff line.
|
||||
/// </summary>
|
||||
public class DiffLineViewModel
|
||||
{
|
||||
public DiffLineViewModel(DiffLine line)
|
||||
{
|
||||
OldLineNumber = line.OldLineNumber?.ToString() ?? "";
|
||||
NewLineNumber = line.NewLineNumber?.ToString() ?? "";
|
||||
Text = line.Text;
|
||||
Type = line.Type;
|
||||
Background = line.Type switch
|
||||
{
|
||||
DiffLineType.Added => "#1A3DD68C",
|
||||
DiffLineType.Removed => "#1AFF6B6B",
|
||||
_ => "Transparent"
|
||||
};
|
||||
BorderColor = line.Type switch
|
||||
{
|
||||
DiffLineType.Added => "#3DD68C",
|
||||
DiffLineType.Removed => "#FF6B6B",
|
||||
_ => "Transparent"
|
||||
};
|
||||
}
|
||||
|
||||
public string OldLineNumber { get; }
|
||||
public string NewLineNumber { get; }
|
||||
public string Text { get; }
|
||||
public DiffLineType Type { get; }
|
||||
public string Background { get; }
|
||||
public string BorderColor { get; }
|
||||
}
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows.Input;
|
||||
using JdeScoping.ConfigManager.Services;
|
||||
|
||||
namespace JdeScoping.ConfigManager.ViewModels.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for the validation results dialog.
|
||||
/// </summary>
|
||||
public class ValidationResultsDialogViewModel : ViewModelBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValidationResultsDialogViewModel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="appSettingsResult">The validation result for appsettings.json.</param>
|
||||
/// <param name="pipelinesResult">The validation result for pipelines.json.</param>
|
||||
public ValidationResultsDialogViewModel(ValidationResult appSettingsResult, ValidationResult pipelinesResult)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(appSettingsResult);
|
||||
ArgumentNullException.ThrowIfNull(pipelinesResult);
|
||||
|
||||
var items = new List<ValidationItemViewModel>();
|
||||
|
||||
foreach (var error in appSettingsResult.Errors)
|
||||
items.Add(new ValidationItemViewModel(error, "appsettings.json", ValidationItemType.Error));
|
||||
foreach (var warning in appSettingsResult.Warnings)
|
||||
items.Add(new ValidationItemViewModel(warning, "appsettings.json", ValidationItemType.Warning));
|
||||
foreach (var error in pipelinesResult.Errors)
|
||||
items.Add(new ValidationItemViewModel(error, "pipelines.json", ValidationItemType.Error));
|
||||
foreach (var warning in pipelinesResult.Warnings)
|
||||
items.Add(new ValidationItemViewModel(warning, "pipelines.json", ValidationItemType.Warning));
|
||||
|
||||
Items = new ObservableCollection<ValidationItemViewModel>(items);
|
||||
ErrorCount = appSettingsResult.Errors.Count + pipelinesResult.Errors.Count;
|
||||
WarningCount = appSettingsResult.Warnings.Count + pipelinesResult.Warnings.Count;
|
||||
IsValid = ErrorCount == 0 && WarningCount == 0;
|
||||
|
||||
CloseCommand = new RelayCommand(() => RequestClose?.Invoke());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collection of validation items to display.
|
||||
/// </summary>
|
||||
public ObservableCollection<ValidationItemViewModel> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total count of validation errors.
|
||||
/// </summary>
|
||||
public int ErrorCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the total count of validation warnings.
|
||||
/// </summary>
|
||||
public int WarningCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether all validation passed (no errors or warnings).
|
||||
/// </summary>
|
||||
public bool IsValid { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command to close the dialog.
|
||||
/// </summary>
|
||||
public ICommand CloseCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the action to request closing the dialog.
|
||||
/// </summary>
|
||||
public Action? RequestClose { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the type of validation item.
|
||||
/// </summary>
|
||||
public enum ValidationItemType
|
||||
{
|
||||
/// <summary>
|
||||
/// A validation error that must be fixed.
|
||||
/// </summary>
|
||||
Error,
|
||||
|
||||
/// <summary>
|
||||
/// A validation warning that should be reviewed.
|
||||
/// </summary>
|
||||
Warning
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ViewModel for a single validation item.
|
||||
/// </summary>
|
||||
public class ValidationItemViewModel
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValidationItemViewModel"/> class.
|
||||
/// </summary>
|
||||
/// <param name="message">The validation message.</param>
|
||||
/// <param name="source">The source file name.</param>
|
||||
/// <param name="type">The type of validation item.</param>
|
||||
public ValidationItemViewModel(string message, string source, ValidationItemType type)
|
||||
{
|
||||
Message = message;
|
||||
Source = source;
|
||||
Type = type;
|
||||
Icon = type == ValidationItemType.Error ? "\u2717" : "\u26A0";
|
||||
IconColor = type == ValidationItemType.Error ? "#FF6B6B" : "#FFB84D";
|
||||
Background = type == ValidationItemType.Error ? "#1AFF6B6B" : "#1AFFB84D";
|
||||
BorderColor = type == ValidationItemType.Error ? "#FF6B6B" : "#FFB84D";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the validation message.
|
||||
/// </summary>
|
||||
public string Message { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the source file name.
|
||||
/// </summary>
|
||||
public string Source { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of validation item.
|
||||
/// </summary>
|
||||
public ValidationItemType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the icon character for the validation type.
|
||||
/// </summary>
|
||||
public string Icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the icon color as a hex string.
|
||||
/// </summary>
|
||||
public string IconColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the background color as a hex string with alpha.
|
||||
/// </summary>
|
||||
public string Background { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the border color as a hex string.
|
||||
/// </summary>
|
||||
public string BorderColor { get; }
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:JdeScoping.ConfigManager.ViewModels.Dialogs"
|
||||
x:Class="JdeScoping.ConfigManager.Views.Dialogs.DiffPreviewDialog"
|
||||
x:DataType="vm:DiffPreviewDialogViewModel"
|
||||
Title="Preview Changes"
|
||||
Width="800" Height="600"
|
||||
MinWidth="600" MinHeight="400"
|
||||
Background="#151920"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<DockPanel>
|
||||
<!-- Header -->
|
||||
<Border DockPanel.Dock="Top" Background="#1C2128" Padding="24,16"
|
||||
BorderBrush="#2D3540" BorderThickness="0,0,0,1">
|
||||
<TextBlock Text="Preview Changes"
|
||||
Foreground="#E6EDF5" FontSize="18" FontWeight="SemiBold"/>
|
||||
</Border>
|
||||
|
||||
<!-- Footer -->
|
||||
<Border DockPanel.Dock="Bottom" Background="#1C2128" Padding="24,16"
|
||||
BorderBrush="#2D3540" BorderThickness="0,1,0,0">
|
||||
<Grid>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Center">
|
||||
<TextBlock Foreground="#9BA8B8" FontFamily="JetBrains Mono" FontSize="12">
|
||||
<Run Text="{Binding Insertions}"/> insertions, <Run Text="{Binding Deletions}"/> deletions
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
|
||||
<Button Content="Cancel" Command="{Binding CancelCommand}"
|
||||
Background="Transparent" BorderBrush="#3D4550"
|
||||
Foreground="#9BA8B8" Padding="16,8"/>
|
||||
<Button Content="Save Changes" Command="{Binding SaveCommand}"
|
||||
Background="#5C9AFF" Foreground="#0D0F12"
|
||||
Padding="16,8" FontWeight="Medium"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Diff Content -->
|
||||
<ScrollViewer Background="#0D0F12" Padding="0">
|
||||
<ItemsControl ItemsSource="{Binding Lines}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{Binding Background}"
|
||||
BorderBrush="{Binding BorderColor}"
|
||||
BorderThickness="3,0,0,0"
|
||||
Padding="0,2">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="50"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock Grid.Column="0" Text="{Binding OldLineNumber}"
|
||||
Foreground="#5C6A7A" FontFamily="JetBrains Mono" FontSize="12"
|
||||
HorizontalAlignment="Right" Padding="0,0,8,0"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding NewLineNumber}"
|
||||
Foreground="#5C6A7A" FontFamily="JetBrains Mono" FontSize="12"
|
||||
HorizontalAlignment="Right" Padding="0,0,8,0"/>
|
||||
<Border Grid.Column="1" Width="1" Background="#2D3540" HorizontalAlignment="Right"/>
|
||||
<TextBlock Grid.Column="2" Text="{Binding Text}"
|
||||
Foreground="#E6EDF5" FontFamily="JetBrains Mono" FontSize="12"
|
||||
Padding="12,0,0,0"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Avalonia.Controls;
|
||||
using JdeScoping.ConfigManager.ViewModels.Dialogs;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Views.Dialogs;
|
||||
|
||||
public partial class DiffPreviewDialog : Window
|
||||
{
|
||||
public DiffPreviewDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
public DiffPreviewDialog(DiffPreviewDialogViewModel viewModel) : this()
|
||||
{
|
||||
DataContext = viewModel;
|
||||
viewModel.RequestClose = Close;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:JdeScoping.ConfigManager.ViewModels.Dialogs"
|
||||
x:Class="JdeScoping.ConfigManager.Views.Dialogs.ValidationResultsDialog"
|
||||
x:DataType="vm:ValidationResultsDialogViewModel"
|
||||
Title="Validation Results"
|
||||
Width="600" Height="500"
|
||||
MinWidth="400" MinHeight="300"
|
||||
Background="#151920"
|
||||
WindowStartupLocation="CenterOwner">
|
||||
|
||||
<DockPanel>
|
||||
<!-- Header -->
|
||||
<Border DockPanel.Dock="Top" Background="#1C2128" Padding="24,16"
|
||||
BorderBrush="#2D3540" BorderThickness="0,0,0,1">
|
||||
<StackPanel>
|
||||
<TextBlock Text="Validation Results"
|
||||
Foreground="#E6EDF5" FontSize="18" FontWeight="SemiBold"/>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,8,0,0" Spacing="16">
|
||||
<TextBlock Foreground="#FF6B6B" FontSize="12">
|
||||
<Run Text="{Binding ErrorCount}"/> errors
|
||||
</TextBlock>
|
||||
<TextBlock Foreground="#FFB84D" FontSize="12">
|
||||
<Run Text="{Binding WarningCount}"/> warnings
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Footer -->
|
||||
<Border DockPanel.Dock="Bottom" Background="#1C2128" Padding="24,16"
|
||||
BorderBrush="#2D3540" BorderThickness="0,1,0,0">
|
||||
<Button Content="Close" Command="{Binding CloseCommand}"
|
||||
HorizontalAlignment="Right"
|
||||
Background="#5C9AFF" Foreground="#0D0F12"
|
||||
Padding="16,8" FontWeight="Medium"/>
|
||||
</Border>
|
||||
|
||||
<!-- Content -->
|
||||
<ScrollViewer Background="#0D0F12" Padding="16">
|
||||
<ItemsControl ItemsSource="{Binding Items}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border Background="{Binding Background}"
|
||||
BorderBrush="{Binding BorderColor}"
|
||||
BorderThickness="3,0,0,0"
|
||||
Margin="0,0,0,8"
|
||||
Padding="12">
|
||||
<StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock Text="{Binding Icon}"
|
||||
Foreground="{Binding IconColor}"
|
||||
FontSize="14"/>
|
||||
<TextBlock Text="{Binding Source}"
|
||||
Foreground="#5C6A7A"
|
||||
FontFamily="JetBrains Mono" FontSize="11"/>
|
||||
</StackPanel>
|
||||
<TextBlock Text="{Binding Message}"
|
||||
Foreground="#E6EDF5"
|
||||
TextWrapping="Wrap"
|
||||
Margin="22,4,0,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollViewer>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
@@ -0,0 +1,28 @@
|
||||
using Avalonia.Controls;
|
||||
using JdeScoping.ConfigManager.ViewModels.Dialogs;
|
||||
|
||||
namespace JdeScoping.ConfigManager.Views.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// Dialog window for displaying validation results.
|
||||
/// </summary>
|
||||
public partial class ValidationResultsDialog : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValidationResultsDialog"/> class.
|
||||
/// </summary>
|
||||
public ValidationResultsDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ValidationResultsDialog"/> class with a view model.
|
||||
/// </summary>
|
||||
/// <param name="viewModel">The view model containing validation results.</param>
|
||||
public ValidationResultsDialog(ValidationResultsDialogViewModel viewModel) : this()
|
||||
{
|
||||
DataContext = viewModel;
|
||||
viewModel.RequestClose = Close;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user