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:
@@ -0,0 +1,83 @@
|
||||
using System.Windows.Controls;
|
||||
using ClaudeDo.Installer.Core;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace ClaudeDo.Installer.Pages.UiSettingsPage;
|
||||
|
||||
public partial class UiSettingsPageViewModel : ObservableObject, IInstallerPage
|
||||
{
|
||||
private readonly InstallContext _context;
|
||||
private UiSettingsPageView? _view;
|
||||
|
||||
public string Title => "UI Settings";
|
||||
public string Icon => "\uE771";
|
||||
public int Order => 3;
|
||||
public bool ShowInWizard => true;
|
||||
public bool ShowInSettings => true;
|
||||
public UserControl View => _view ??= new UiSettingsPageView { DataContext = this };
|
||||
|
||||
[ObservableProperty] private string _signalRUrl = "http://127.0.0.1:47821/hub";
|
||||
[ObservableProperty] private string _uiDbPath = "~/.todo-app/todo.db";
|
||||
[ObservableProperty] private bool _isSynced = true;
|
||||
[ObservableProperty] private string? _validationError;
|
||||
|
||||
public UiSettingsPageViewModel(InstallContext context) => _context = context;
|
||||
|
||||
partial void OnIsSyncedChanged(bool value)
|
||||
{
|
||||
if (value) SyncFromContext();
|
||||
}
|
||||
|
||||
private void SyncFromContext()
|
||||
{
|
||||
SignalRUrl = $"http://127.0.0.1:{_context.SignalRPort}/hub";
|
||||
UiDbPath = _context.DbPath;
|
||||
}
|
||||
|
||||
public Task LoadAsync()
|
||||
{
|
||||
if (IsSynced)
|
||||
{
|
||||
SyncFromContext();
|
||||
}
|
||||
else
|
||||
{
|
||||
var cfg = InstallerAppSettings.Load();
|
||||
SignalRUrl = cfg.SignalRUrl;
|
||||
UiDbPath = cfg.DbPath;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task ApplyAsync()
|
||||
{
|
||||
if (IsSynced) SyncFromContext();
|
||||
_context.SignalRUrl = SignalRUrl;
|
||||
_context.UiDbPath = UiDbPath;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public bool Validate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(SignalRUrl))
|
||||
{
|
||||
ValidationError = "SignalR URL is required.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(UiDbPath))
|
||||
{
|
||||
ValidationError = "Database path is required.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Uri.TryCreate(SignalRUrl, UriKind.Absolute, out _))
|
||||
{
|
||||
ValidationError = "SignalR URL must be a valid URL.";
|
||||
return false;
|
||||
}
|
||||
|
||||
ValidationError = null;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user