WIP and refactor start on Installer Config Dialog

This commit is contained in:
2026-05-05 19:33:07 -05:00
parent 256fd5dca8
commit 888d856c4d
17 changed files with 642 additions and 320 deletions
+1
View File
@@ -7,3 +7,4 @@
**/obj/
vm/
.vs/
Binary file not shown.
@@ -2,6 +2,10 @@
"Version": 1,
"WorkspaceRootPath": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\mainwindow.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:mainwindow.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
@@ -18,11 +22,24 @@
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 1,
"SelectedChildIndex": 0,
"Children": [
{
"$type": "Document",
"DocumentIndex": 1,
"DocumentIndex": 0,
"Title": "MainWindow.cs",
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\MainWindow.cs",
"RelativeDocumentMoniker": "MainWindow.cs",
"ToolTip": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\MainWindow.cs",
"RelativeToolTip": "MainWindow.cs",
"ViewState": "AgIAABsBAAAAAAAAAIA5wBsBAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-05T23:22:18.637Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 2,
"Title": "Program.cs",
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\Program.cs",
"RelativeDocumentMoniker": "Program.cs",
@@ -30,12 +47,11 @@
"RelativeToolTip": "Program.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-05T00:32:51.733Z",
"EditorCaption": ""
"WhenOpened": "2026-05-05T00:32:51.733Z"
},
{
"$type": "Document",
"DocumentIndex": 0,
"DocumentIndex": 1,
"Title": "App.xaml",
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\App.xaml",
"RelativeDocumentMoniker": "App.xaml",
@@ -2,6 +2,14 @@
"Version": 1,
"WorkspaceRootPath": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\installerconfigdialog.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:installerconfigdialog.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
},
{
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\mainwindow.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}",
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:mainwindow.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}"
},
{
"AbsoluteMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|c:\\users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}",
"RelativeMoniker": "D:0:0:{5D26E66D-8F74-39AC-8CD2-24BC7C6E0C0F}|Covenant.Setup.Authoring.csproj|solutionrelative:app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}"
@@ -18,11 +26,37 @@
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 1,
"SelectedChildIndex": 0,
"Children": [
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "InstallerConfigDialog.xaml",
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\InstallerConfigDialog.xaml",
"RelativeDocumentMoniker": "InstallerConfigDialog.xaml",
"ToolTip": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\InstallerConfigDialog.xaml*",
"RelativeToolTip": "InstallerConfigDialog.xaml*",
"ViewState": "AgIAABwAAAAAAAAAAAAAACMAAAAZAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|",
"WhenOpened": "2026-05-06T00:18:02.89Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 1,
"Title": "MainWindow.cs",
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\MainWindow.cs",
"RelativeDocumentMoniker": "MainWindow.cs",
"ToolTip": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\MainWindow.cs",
"RelativeToolTip": "MainWindow.cs",
"ViewState": "AgIAABsBAAAAAAAAAIA5wBsBAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-05T23:22:18.637Z",
"EditorCaption": ""
},
{
"$type": "Document",
"DocumentIndex": 3,
"Title": "Program.cs",
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\Program.cs",
"RelativeDocumentMoniker": "Program.cs",
@@ -30,12 +64,11 @@
"RelativeToolTip": "Program.cs",
"ViewState": "AgIAAAAAAAAAAAAAAAAAAAsAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|",
"WhenOpened": "2026-05-05T00:32:51.733Z",
"EditorCaption": ""
"WhenOpened": "2026-05-05T00:32:51.733Z"
},
{
"$type": "Document",
"DocumentIndex": 0,
"DocumentIndex": 2,
"Title": "App.xaml",
"DocumentMoniker": "C:\\Users\\jasonross\\workspace\\covenant-setup\\authoring-ui\\App.xaml",
"RelativeDocumentMoniker": "App.xaml",
+71
View File
@@ -0,0 +1,71 @@
<ContentDialog
x:Class="Covenant.Setup.Authoring.InstallerConfigDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Covenant.Setup.Authoring"
Title="Installer Config"
PrimaryButtonText="Save"
IsPrimaryButtonEnabled="{Binding CanSaveConfig, Mode=OneWay}"
PrimaryButtonClick="SaveButton_Click"
CloseButtonText="Close">
<Grid
MinWidth="640"
ColumnSpacing="8"
RowSpacing="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Grid.ColumnSpan="2"
Text="{Binding StatusText}"
TextWrapping="Wrap" />
<TextBox
Grid.Row="1"
Grid.Column="0"
MaxWidth="200"
Header="Covenant Setup Executable"
PlaceholderText="C:\path\to\covenant-setup.exe"
Text="{Binding CovenantSetupPath, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button
Grid.Row="1"
Grid.Column="1"
MinWidth="96"
VerticalAlignment="Bottom"
Content="Browse"
Click="BrowseCovenantSetupButton_Click" />
<TextBox
Grid.Row="2"
Grid.Column="0"
MaxWidth="200"
Header="Output Directory"
PlaceholderText="C:\path\to\dist"
Text="{Binding OutputDirectory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button
Grid.Row="2"
Grid.Column="1"
MinWidth="96"
VerticalAlignment="Bottom"
Content="Choose"
Click="ChooseOutputDirectoryButton_Click" />
<TextBlock
Grid.Row="3"
Grid.ColumnSpan="2"
Text="{Binding MessageText}"
TextWrapping="Wrap" />
</Grid>
</ContentDialog>
@@ -0,0 +1,64 @@
using Microsoft.UI.Xaml.Controls;
using Windows.Storage.Pickers;
using WinRT.Interop;
namespace Covenant.Setup.Authoring;
internal sealed partial class InstallerConfigDialog : ContentDialog
{
private readonly InstallerConfigViewModel _viewModel;
private readonly IntPtr _ownerWindowHandle;
public InstallerConfigDialog(InstallerConfigViewModel viewModel, IntPtr ownerWindowHandle)
{
_viewModel = viewModel;
_ownerWindowHandle = ownerWindowHandle;
InitializeComponent();
DataContext = viewModel;
}
private async void BrowseCovenantSetupButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs args)
{
var picker = new FileOpenPicker
{
SuggestedStartLocation = PickerLocationId.ComputerFolder
};
InitializeWithWindow.Initialize(picker, _ownerWindowHandle);
picker.FileTypeFilter.Add(".exe");
var file = await picker.PickSingleFileAsync();
if (file is not null)
{
_ = await _viewModel.AcceptToolPathAsync(file.Path);
}
}
private async void ChooseOutputDirectoryButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs args)
{
var picker = new FolderPicker
{
SuggestedStartLocation = PickerLocationId.DocumentsLibrary
};
InitializeWithWindow.Initialize(picker, _ownerWindowHandle);
picker.FileTypeFilter.Add("*");
var folder = await picker.PickSingleFolderAsync();
if (folder is not null)
{
_viewModel.OutputDirectory = folder.Path;
}
}
private async void SaveButton_Click(ContentDialog sender, ContentDialogButtonClickEventArgs args)
{
var deferral = args.GetDeferral();
try
{
args.Cancel = !await _viewModel.SaveConfigAsync();
}
finally
{
deferral.Complete();
}
}
}
+434
View File
@@ -0,0 +1,434 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace Covenant.Setup.Authoring;
internal enum InstallerConfigStatusKind
{
Neutral,
Success,
Error
}
internal sealed class InstallerConfigViewModel : INotifyPropertyChanged
{
private const string MissingToolStatus = "covenant-setup.exe was not found. Packaging is disabled.";
private const string UnvalidatedToolStatus = "covenant-setup.exe was not validated. Packaging is disabled.";
private readonly Func<CovenantSetupTool?> _locateCovenantSetupTool;
private readonly Func<string, CancellationToken, Task<ToolValidationResult>> _validateCovenantSetupTool;
private CovenantSetupTool? _covenantSetupTool;
private string _covenantSetupPath = string.Empty;
private string _outputDirectory = Path.Combine(Environment.CurrentDirectory, "dist");
private string _statusText = MissingToolStatus;
private string _messageText = string.Empty;
private InstallerConfigStatusKind _statusKind = InstallerConfigStatusKind.Error;
private InstallerConfigStatusKind _messageKind = InstallerConfigStatusKind.Neutral;
private bool _isCheckingTool;
private bool _isBuilding;
private bool _hasManifestValidationErrors;
private bool _syncingAcceptedToolPath;
public InstallerConfigViewModel()
: this(CovenantSetupToolLocator.Find, CovenantSetupToolValidator.ValidateAsync)
{
}
internal InstallerConfigViewModel(
Func<CovenantSetupTool?> locateCovenantSetupTool,
Func<string, CancellationToken, Task<ToolValidationResult>> validateCovenantSetupTool)
{
_locateCovenantSetupTool = locateCovenantSetupTool;
_validateCovenantSetupTool = validateCovenantSetupTool;
RefreshToolCheckCommand = new RelayCommand(RefreshToolCheck, () => !IsBusy);
ValidateToolPathCommand = new AsyncRelayCommand(ValidateToolPathAsync, () => CanValidateToolPath);
SaveConfigCommand = new AsyncRelayCommand(SaveConfigAsync, () => CanSaveConfig);
RefreshToolCheck();
}
public event PropertyChangedEventHandler? PropertyChanged;
public ICommand RefreshToolCheckCommand { get; }
public ICommand ValidateToolPathCommand { get; }
public ICommand SaveConfigCommand { get; }
public CovenantSetupTool? CovenantSetupTool
{
get => _covenantSetupTool;
private set
{
if (SetProperty(ref _covenantSetupTool, value))
{
OnPropertyChanged(nameof(HasCovenantSetupTool));
OnPropertyChanged(nameof(CanSaveConfig));
RaiseCommandStatesChanged();
}
}
}
public bool HasCovenantSetupTool => CovenantSetupTool is not null;
public string CovenantSetupPath
{
get => _covenantSetupPath;
set
{
if (!SetProperty(ref _covenantSetupPath, value))
{
return;
}
OnPropertyChanged(nameof(CanValidateToolPath));
RaiseCommandStatesChanged();
if (!_syncingAcceptedToolPath && !IsAcceptedToolPath(value))
{
ClearValidatedTool(UnvalidatedToolStatus);
}
}
}
public string OutputDirectory
{
get => _outputDirectory;
set
{
if (SetProperty(ref _outputDirectory, value))
{
OnPropertyChanged(nameof(CanSaveConfig));
RaiseCommandStatesChanged();
}
}
}
public string StatusText
{
get => _statusText;
private set => SetProperty(ref _statusText, value);
}
public InstallerConfigStatusKind StatusKind
{
get => _statusKind;
private set => SetProperty(ref _statusKind, value);
}
public string MessageText
{
get => _messageText;
private set => SetProperty(ref _messageText, value);
}
public InstallerConfigStatusKind MessageKind
{
get => _messageKind;
private set => SetProperty(ref _messageKind, value);
}
public bool IsCheckingTool
{
get => _isCheckingTool;
private set
{
if (SetProperty(ref _isCheckingTool, value))
{
NotifyBusyStateChanged();
}
}
}
public bool IsBuilding
{
get => _isBuilding;
set
{
if (SetProperty(ref _isBuilding, value))
{
NotifyBusyStateChanged();
}
}
}
public bool IsBusy => IsCheckingTool || IsBuilding;
public bool HasManifestValidationErrors
{
get => _hasManifestValidationErrors;
private set
{
if (SetProperty(ref _hasManifestValidationErrors, value))
{
OnPropertyChanged(nameof(CanSaveConfig));
RaiseCommandStatesChanged();
}
}
}
public bool CanValidateToolPath =>
!IsBusy &&
!string.IsNullOrWhiteSpace(CovenantSetupPath);
public bool CanSaveConfig =>
!IsBusy &&
!HasManifestValidationErrors &&
!string.IsNullOrWhiteSpace(CovenantSetupPath) &&
!string.IsNullOrWhiteSpace(OutputDirectory);
public void RefreshToolCheck()
{
if (IsBusy)
{
return;
}
var tool = _locateCovenantSetupTool();
if (tool is null)
{
ClearValidatedTool(MissingToolStatus);
SetMessage(string.Empty, InstallerConfigStatusKind.Neutral);
return;
}
AcceptValidatedTool(tool);
SetMessage(string.Empty, InstallerConfigStatusKind.Neutral);
}
public async Task<bool> ValidateToolPathAsync(CancellationToken cancellationToken = default)
{
return await AcceptToolPathAsync(CovenantSetupPath, cancellationToken);
}
public async Task<bool> AcceptToolPathAsync(
string path,
CancellationToken cancellationToken = default)
{
if (IsBusy)
{
return false;
}
if (string.IsNullOrWhiteSpace(path))
{
SyncToolPath(string.Empty);
ClearValidatedTool(MissingToolStatus);
SetMessage(string.Empty, InstallerConfigStatusKind.Neutral);
return false;
}
IsCheckingTool = true;
SetMessage("Checking covenant-setup...", InstallerConfigStatusKind.Neutral);
try
{
SyncToolPath(path.Trim());
var result = await _validateCovenantSetupTool(CovenantSetupPath, cancellationToken);
if (result.IsValid && result.Tool is not null)
{
AcceptValidatedTool(result.Tool);
SetMessage(string.Empty, InstallerConfigStatusKind.Neutral);
return true;
}
ClearValidatedTool(result.Message);
SetMessage(
result.Message + " Pick a valid covenant-setup executable.",
InstallerConfigStatusKind.Error);
return false;
}
finally
{
IsCheckingTool = false;
}
}
public void SetManifestValidationState(bool hasErrors)
{
HasManifestValidationErrors = hasErrors;
}
public async Task<bool> SaveConfigAsync(CancellationToken cancellationToken = default)
{
if (IsBuilding)
{
SetMessage("Installer configuration cannot be saved while a build is running.", InstallerConfigStatusKind.Error);
return false;
}
if (HasManifestValidationErrors)
{
SetMessage("Resolve manifest validation errors before saving installer configuration.", InstallerConfigStatusKind.Error);
return false;
}
if (!IsAcceptedToolPath(CovenantSetupPath))
{
var accepted = await ValidateToolPathAsync(cancellationToken);
if (!accepted)
{
return false;
}
}
if (string.IsNullOrWhiteSpace(OutputDirectory))
{
SetMessage("Choose an output directory before saving installer configuration.", InstallerConfigStatusKind.Error);
return false;
}
SetMessage("Installer configuration saved.", InstallerConfigStatusKind.Success);
return true;
}
public void ClearMessage()
{
SetMessage(string.Empty, InstallerConfigStatusKind.Neutral);
}
private void AcceptValidatedTool(CovenantSetupTool tool)
{
CovenantSetupTool = tool;
SyncToolPath(tool.Path);
StatusText = "Packaging enabled: " + tool.Path;
StatusKind = InstallerConfigStatusKind.Success;
}
private void ClearValidatedTool(string statusText)
{
CovenantSetupTool = null;
StatusText = statusText;
StatusKind = InstallerConfigStatusKind.Error;
}
private void SyncToolPath(string path)
{
_syncingAcceptedToolPath = true;
try
{
CovenantSetupPath = path;
}
finally
{
_syncingAcceptedToolPath = false;
}
}
private bool IsAcceptedToolPath(string path) =>
CovenantSetupTool is not null &&
string.Equals(path?.Trim(), CovenantSetupTool.Path, StringComparison.OrdinalIgnoreCase);
private void SetMessage(string message, InstallerConfigStatusKind kind)
{
MessageText = message;
MessageKind = kind;
}
private void NotifyBusyStateChanged()
{
OnPropertyChanged(nameof(IsBusy));
OnPropertyChanged(nameof(CanValidateToolPath));
OnPropertyChanged(nameof(CanSaveConfig));
RaiseCommandStatesChanged();
}
private bool SetProperty<T>(
ref T field,
T value,
[CallerMemberName] string? propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
if (propertyName is not null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
private void RaiseCommandStatesChanged()
{
((RelayCommand)RefreshToolCheckCommand).RaiseCanExecuteChanged();
((AsyncRelayCommand)ValidateToolPathCommand).RaiseCanExecuteChanged();
((AsyncRelayCommand)SaveConfigCommand).RaiseCanExecuteChanged();
}
private sealed class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool>? _canExecute;
public RelayCommand(Action execute, Func<bool>? canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) => _canExecute?.Invoke() ?? true;
public void Execute(object? parameter) => _execute();
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
private sealed class AsyncRelayCommand : ICommand
{
private readonly Func<CancellationToken, Task> _execute;
private readonly Func<bool>? _canExecute;
private CancellationTokenSource? _currentExecution;
public AsyncRelayCommand(
Func<CancellationToken, Task> execute,
Func<bool>? canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler? CanExecuteChanged;
public bool CanExecute(object? parameter) =>
_currentExecution is null &&
(_canExecute?.Invoke() ?? true);
public async void Execute(object? parameter)
{
if (!CanExecute(parameter))
{
return;
}
_currentExecution = new CancellationTokenSource();
RaiseCanExecuteChanged();
try
{
await _execute(_currentExecution.Token);
}
finally
{
_currentExecution?.Dispose();
_currentExecution = null;
RaiseCanExecuteChanged();
}
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
}
+14 -311
View File
@@ -20,19 +20,13 @@ namespace Covenant.Setup.Authoring;
internal sealed class MainWindow : Window
{
private readonly MainViewModel _viewModel = new();
private readonly InstallerConfigViewModel _installerConfigViewModel = new();
private readonly List<Border> _sectionBorders = [];
private readonly List<Border> _rowBorders = [];
private readonly List<TextBlock> _secondaryTextBlocks = [];
private TextBlock _statusText = null!;
private TextBlock _toolStatusText = null!;
private TextBlock _installerDialogMessageText = null!;
private TextBox _previewBox = null!;
private Button _copyPreviewButton = null!;
private TextBox _covenantSetupPathBox = null!;
private Button _browseCovenantSetupButton = null!;
private Button _refreshToolButton = null!;
private TextBox _outputDirectoryBox = null!;
private Button _chooseOutputButton = null!;
private Button _generateInstallerButton = null!;
private Grid _rootGrid = null!;
private ToggleSwitch _themeToggle = null!;
@@ -41,7 +35,6 @@ internal sealed class MainWindow : Window
private int _copyPreviewFeedbackVersion;
private bool _syncingThemeToggle;
private bool _usingSystemTheme;
private bool _syncingCovenantSetupPath;
public MainWindow()
{
@@ -56,10 +49,7 @@ internal sealed class MainWindow : Window
RefreshStatusBrush();
}
if (args.PropertyName is nameof(MainViewModel.HasCovenantSetupTool)
or nameof(MainViewModel.CanPackage)
or nameof(MainViewModel.CovenantSetupStatus)
or nameof(MainViewModel.CovenantSetupPath)
or nameof(MainViewModel.OutputDirectory))
or nameof(MainViewModel.CanPackage))
{
RefreshPackageControls();
}
@@ -291,97 +281,6 @@ internal sealed class MainWindow : Window
Labeled("Primary Payload", payloadBox));
}
private FrameworkElement BuildInstallerConfigContent()
{
_toolStatusText = new TextBlock
{
TextWrapping = TextWrapping.Wrap
};
_toolStatusText.SetBinding(
TextBlock.TextProperty,
new Binding
{
Path = new PropertyPath(nameof(MainViewModel.CovenantSetupStatus)),
Mode = BindingMode.OneWay
});
_covenantSetupPathBox = new TextBox
{
PlaceholderText = @"C:\path\to\covenant-setup.exe",
Text = _viewModel.CovenantSetupPath
};
_covenantSetupPathBox.TextChanged += (_, _) =>
{
if (_syncingCovenantSetupPath)
{
return;
}
if (!string.Equals(_covenantSetupPathBox.Text, _viewModel.CovenantSetupPath, StringComparison.OrdinalIgnoreCase))
{
_viewModel.RejectCovenantSetupTool("covenant-setup.exe was not validated. Packaging is disabled.");
}
};
_covenantSetupPathBox.LostFocus += async (_, _) => await ValidateTypedCovenantSetupPathAsync(showInvalidMessage: true);
_browseCovenantSetupButton = new Button
{
Content = "Browse",
Width = 96
};
_browseCovenantSetupButton.Click += async (_, _) => await BrowseCovenantSetupPathAsync();
_outputDirectoryBox = BoundTextBox(nameof(MainViewModel.OutputDirectory), @"C:\path\to\dist");
_chooseOutputButton = new Button
{
Content = "Choose",
Width = 96
};
_chooseOutputButton.Click += async (_, _) => await ChooseOutputDirectoryAsync();
_refreshToolButton = new Button
{
Content = "Refresh Tool Check",
Width = 156
};
_refreshToolButton.Click += (_, _) =>
{
_viewModel.RefreshCovenantSetupTool();
SyncCovenantSetupPathBox();
SetInstallerDialogMessage(string.Empty);
};
var actionRow = new StackPanel
{
Orientation = Orientation.Horizontal,
HorizontalAlignment = HorizontalAlignment.Right,
Spacing = 8
};
actionRow.Children.Add(_refreshToolButton);
_installerDialogMessageText = new TextBlock
{
TextWrapping = TextWrapping.Wrap,
MaxLines = 6
};
_secondaryTextBlocks.Add(_installerDialogMessageText);
var stack = new StackPanel
{
Spacing = 10,
MinWidth = 640,
HorizontalAlignment = HorizontalAlignment.Stretch
};
stack.Children.Add(_toolStatusText);
stack.Children.Add(DialogFieldGrid(
("Covenant Setup Executable", _covenantSetupPathBox, _browseCovenantSetupButton),
("Output Directory", _outputDirectoryBox, _chooseOutputButton)));
stack.Children.Add(actionRow);
stack.Children.Add(_installerDialogMessageText);
return stack;
}
private FrameworkElement BuildDirectoriesSection()
{
var pathBox = new TextBox { PlaceholderText = @"{LocalAppData}\Vendor\App" };
@@ -505,42 +404,24 @@ internal sealed class MainWindow : Window
private async Task ShowInstallerConfigAsync()
{
var dialog = new ContentDialog
{
Title = "Installer Config",
CloseButtonText = "Close",
Width = 1080,
MinWidth = 1080,
Content = BuildInstallerConfigContent()
};
_installerConfigViewModel.SetManifestValidationState(_viewModel.HasValidationErrors);
_installerConfigViewModel.IsBuilding = _isPackaging;
var dialog = new InstallerConfigDialog(
_installerConfigViewModel,
WindowNative.GetWindowHandle(this));
if (Content is FrameworkElement root)
{
dialog.XamlRoot = root.XamlRoot;
}
dialog.Closing += (_, args) =>
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
if (_isPackaging)
{
args.Cancel = true;
}
};
RefreshPackageControls();
_ = await dialog.ShowAsync();
if (_installerDialogMessageText is not null)
{
_secondaryTextBlocks.Remove(_installerDialogMessageText);
_viewModel.SetCovenantSetupTool(_installerConfigViewModel.CovenantSetupTool);
_viewModel.OutputDirectory = _installerConfigViewModel.OutputDirectory;
RefreshPackageControls();
}
_toolStatusText = null!;
_installerDialogMessageText = null!;
_covenantSetupPathBox = null!;
_browseCovenantSetupButton = null!;
_refreshToolButton = null!;
_outputDirectoryBox = null!;
_chooseOutputButton = null!;
}
private async Task SaveManifestAsync()
@@ -614,95 +495,6 @@ internal sealed class MainWindow : Window
return file.Path;
}
private async Task BrowseCovenantSetupPathAsync()
{
var picker = new FileOpenPicker();
InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
picker.SuggestedStartLocation = PickerLocationId.ComputerFolder;
picker.FileTypeFilter.Add(".exe");
var file = await picker.PickSingleFileAsync();
if (file is not null)
{
await AcceptCovenantSetupPathAsync(file.Path, showInvalidMessage: true);
}
}
private async Task ValidateTypedCovenantSetupPathAsync(bool showInvalidMessage)
{
if (_covenantSetupPathBox is null)
{
return;
}
var path = _covenantSetupPathBox.Text.Trim();
if (string.IsNullOrWhiteSpace(path))
{
_viewModel.RejectCovenantSetupTool("covenant-setup.exe was not found. Packaging is disabled.");
SetInstallerDialogMessage(string.Empty);
return;
}
if (string.Equals(path, _viewModel.CovenantSetupPath, StringComparison.OrdinalIgnoreCase))
{
return;
}
await AcceptCovenantSetupPathAsync(path, showInvalidMessage);
}
private async Task<bool> AcceptCovenantSetupPathAsync(string path, bool showInvalidMessage)
{
SetInstallerDialogMessage("Checking covenant-setup...");
var result = await CovenantSetupToolValidator.ValidateAsync(path, CancellationToken.None);
if (result.IsValid && result.Tool is not null)
{
_viewModel.SetCovenantSetupTool(result.Tool);
SyncCovenantSetupPathBox();
SetInstallerDialogMessage(string.Empty);
return true;
}
_viewModel.RejectCovenantSetupTool(result.Message);
if (showInvalidMessage)
{
SetInstallerDialogMessage(result.Message + " Run the help check by selecting the real covenant-setup.exe.");
}
return false;
}
private void SyncCovenantSetupPathBox()
{
if (_covenantSetupPathBox is null)
{
return;
}
_syncingCovenantSetupPath = true;
try
{
_covenantSetupPathBox.Text = _viewModel.CovenantSetupPath;
}
finally
{
_syncingCovenantSetupPath = false;
}
}
private async Task ChooseOutputDirectoryAsync()
{
var picker = new FolderPicker();
InitializeWithWindow.Initialize(picker, WindowNative.GetWindowHandle(this));
picker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
picker.FileTypeFilter.Add("*");
var folder = await picker.PickSingleFolderAsync();
if (folder is not null)
{
_viewModel.OutputDirectory = folder.Path;
}
}
private async Task GenerateInstallerAsync(Func<string, string, Task>? showMessageAsync = null)
{
showMessageAsync ??= ShowNoticeAsync;
@@ -734,6 +526,7 @@ internal sealed class MainWindow : Window
_lastSavedManifestPath = manifestPath;
_isPackaging = true;
_installerConfigViewModel.IsBuilding = true;
RefreshPackageControls();
try
{
@@ -758,6 +551,7 @@ internal sealed class MainWindow : Window
finally
{
_isPackaging = false;
_installerConfigViewModel.IsBuilding = false;
RefreshPackageControls();
}
}
@@ -806,14 +600,6 @@ internal sealed class MainWindow : Window
AutomationProperties.SetName(_copyPreviewButton, "Copy TOML preview");
}
private void SetInstallerDialogMessage(string message)
{
if (_installerDialogMessageText is not null)
{
_installerDialogMessageText.Text = message;
}
}
private async Task ShowNoticeAsync(string title, string message)
{
var dialog = new ContentDialog
@@ -924,39 +710,11 @@ internal sealed class MainWindow : Window
private void RefreshPackageControls()
{
var controlsAvailable = !_isPackaging;
if (_covenantSetupPathBox is not null)
{
_covenantSetupPathBox.IsEnabled = controlsAvailable;
}
if (_browseCovenantSetupButton is not null)
{
_browseCovenantSetupButton.IsEnabled = controlsAvailable;
}
if (_refreshToolButton is not null)
{
_refreshToolButton.IsEnabled = controlsAvailable;
}
if (_outputDirectoryBox is not null)
{
_outputDirectoryBox.IsEnabled = controlsAvailable;
}
if (_chooseOutputButton is not null)
{
_chooseOutputButton.IsEnabled = controlsAvailable;
}
if (_generateInstallerButton is not null)
{
_generateInstallerButton.IsEnabled = _viewModel.HasCovenantSetupTool && !_isPackaging;
_generateInstallerButton.Content = _isPackaging ? "Building..." : "Save and Build";
}
var brushes = CreateThemeBrushes();
if (_toolStatusText is not null)
{
_toolStatusText.Foreground = _viewModel.HasCovenantSetupTool
? brushes.SuccessText
: brushes.ErrorText;
}
}
private void ApplyThemeBrushes()
@@ -994,20 +752,6 @@ internal sealed class MainWindow : Window
}
RefreshStatusBrush();
RefreshPackageStatusBrush();
}
private void RefreshPackageStatusBrush()
{
if (_toolStatusText is null)
{
return;
}
var brushes = CreateThemeBrushes();
_toolStatusText.Foreground = _viewModel.HasCovenantSetupTool
? brushes.SuccessText
: brushes.ErrorText;
}
private ElementTheme EffectiveTheme()
@@ -1123,47 +867,6 @@ internal sealed class MainWindow : Window
return stack;
}
private static Grid DialogFieldGrid(params (string Label, TextBox TextBox, Button Button)[] fields)
{
var grid = new Grid
{
ColumnSpacing = 6,
RowSpacing = 2,
HorizontalAlignment = HorizontalAlignment.Stretch
};
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
for (var index = 0; index < fields.Length; index++)
{
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
var label = new TextBlock
{
Text = fields[index].Label,
FontWeight = FontWeights.SemiBold,
FontSize = 12
};
Grid.SetRow(label, index * 2);
Grid.SetColumn(label, 0);
grid.Children.Add(label);
fields[index].TextBox.HorizontalAlignment = HorizontalAlignment.Stretch;
Grid.SetRow(fields[index].TextBox, (index * 2) + 1);
Grid.SetColumn(fields[index].TextBox, 0);
grid.Children.Add(fields[index].TextBox);
fields[index].Button.HorizontalAlignment = HorizontalAlignment.Left;
fields[index].Button.VerticalAlignment = VerticalAlignment.Bottom;
Grid.SetRow(fields[index].Button, (index * 2) + 1);
Grid.SetColumn(fields[index].Button, 1);
grid.Children.Add(fields[index].Button);
}
return grid;
}
private static Grid TwoColumn(FrameworkElement left, FrameworkElement right)
{
var grid = new Grid { ColumnSpacing = 6 };