feat(installer): add WPF installer/configurator project
Standalone WPF app (ClaudeDo.Installer) that handles full installation and ongoing configuration of ClaudeDo. Two modes: wizard for first run, tabbed settings panel for subsequent launches. Page-based extensibility via IInstallerPage interface — adding new config sections requires only one new class. Install pipeline: dotnet publish, deploy binaries, write configs, init DB (via SchemaInitializer from ClaudeDo.Data), register Windows Service, create shortcuts. Dark theme matching the Avalonia app (forest teal accent). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
77
src/ClaudeDo.Installer/Views/WizardViewModel.cs
Normal file
77
src/ClaudeDo.Installer/Views/WizardViewModel.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System.Windows;
|
||||
using ClaudeDo.Installer.Core;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
namespace ClaudeDo.Installer.Views;
|
||||
|
||||
public partial class WizardViewModel : ObservableObject
|
||||
{
|
||||
private readonly InstallContext _context;
|
||||
|
||||
public IReadOnlyList<IInstallerPage> Pages { get; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(CanGoBack))]
|
||||
[NotifyPropertyChangedFor(nameof(IsLastPage))]
|
||||
[NotifyPropertyChangedFor(nameof(NextButtonText))]
|
||||
[NotifyPropertyChangedFor(nameof(CurrentPage))]
|
||||
private int _currentPageIndex;
|
||||
|
||||
public IInstallerPage CurrentPage => Pages[CurrentPageIndex];
|
||||
public bool CanGoBack => CurrentPageIndex > 0;
|
||||
public bool IsLastPage => CurrentPageIndex == Pages.Count - 1;
|
||||
public string NextButtonText => IsLastPage ? "Install" : "Next \u2192";
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _validationError;
|
||||
|
||||
public WizardViewModel(PageResolver resolver, InstallContext context)
|
||||
{
|
||||
Pages = resolver.WizardPages;
|
||||
_context = context;
|
||||
|
||||
if (Pages.Count > 0)
|
||||
_ = InitAsync();
|
||||
}
|
||||
|
||||
private async Task InitAsync()
|
||||
{
|
||||
try { await Pages[0].LoadAsync(); }
|
||||
catch { /* first page loads with defaults on error */ }
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task GoBack()
|
||||
{
|
||||
if (!CanGoBack) return;
|
||||
CurrentPageIndex--;
|
||||
await CurrentPage.LoadAsync();
|
||||
ValidationError = null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task GoNext()
|
||||
{
|
||||
if (!CurrentPage.Validate())
|
||||
{
|
||||
ValidationError = "Please fix the highlighted errors before continuing.";
|
||||
return;
|
||||
}
|
||||
|
||||
ValidationError = null;
|
||||
await CurrentPage.ApplyAsync();
|
||||
|
||||
if (CurrentPageIndex < Pages.Count - 1)
|
||||
{
|
||||
CurrentPageIndex++;
|
||||
await CurrentPage.LoadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Close()
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user