- Init Localizer at app startup (before self-update prompt) and assign to TrExtension.Localizer - Register ILocalizer in DI; inject into WizardViewModel and SettingsViewModel - WizardViewModel: SelectedLanguage ComboBox binding with OnSelectedLanguageChanged -> SetLanguage + InstallContext.Language - WizardWindow.xaml: DockPanel wraps step chips + language ComboBox (right-aligned) - Localize all installer XAML: WizardWindow, WelcomePage, PathsPage, ServicePage, UiSettingsPage, InstallPage, SettingsWindow, SelfUpdatePromptWindow - Localize page Title properties and WizardViewModel.NextButtonText via TrExtension.Localizer - Persist chosen Language in WriteConfigStep and SettingsViewModel.Save into ui.config.json - Append installer section to en.json (nav, welcome, paths, service, uiSettings, install, settings, selfUpdate) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
214 lines
6.9 KiB
C#
214 lines
6.9 KiB
C#
using System.Windows;
|
|
using ClaudeDo.Installer.Core;
|
|
using ClaudeDo.Localization;
|
|
using ClaudeDo.Releases;
|
|
using ClaudeDo.Installer.Steps;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
|
|
namespace ClaudeDo.Installer.Views;
|
|
|
|
public partial class SettingsViewModel : ObservableObject
|
|
{
|
|
private readonly InstallContext _context;
|
|
private readonly ILocalizer _localizer;
|
|
private readonly IReleaseClient _releases;
|
|
private readonly StopWorkerStep _stopService;
|
|
private readonly StartWorkerStep _startService;
|
|
private readonly RegisterAutostartStep _registerAutostart;
|
|
private readonly DownloadAndExtractStep _downloadStep;
|
|
private readonly UninstallRunner _uninstallRunner;
|
|
|
|
public IReadOnlyList<IInstallerPage> Pages { get; }
|
|
|
|
[ObservableProperty]
|
|
private IInstallerPage? _selectedPage;
|
|
|
|
[ObservableProperty]
|
|
private string? _statusMessage;
|
|
|
|
[ObservableProperty]
|
|
private bool _isStatusError;
|
|
|
|
[ObservableProperty]
|
|
private string _versionLabel = "";
|
|
|
|
[ObservableProperty]
|
|
private bool _removeAppData;
|
|
|
|
public SettingsViewModel(
|
|
PageResolver resolver,
|
|
InstallContext context,
|
|
ILocalizer localizer,
|
|
IReleaseClient releases,
|
|
StopWorkerStep stopService,
|
|
StartWorkerStep startService,
|
|
RegisterAutostartStep registerAutostart,
|
|
DownloadAndExtractStep downloadStep,
|
|
UninstallRunner uninstallRunner)
|
|
{
|
|
Pages = resolver.SettingsPages;
|
|
_context = context;
|
|
_localizer = localizer;
|
|
_releases = releases;
|
|
_stopService = stopService;
|
|
_startService = startService;
|
|
_registerAutostart = registerAutostart;
|
|
_downloadStep = downloadStep;
|
|
_uninstallRunner = uninstallRunner;
|
|
_selectedPage = Pages.FirstOrDefault();
|
|
|
|
var label = $"Installed: {context.InstalledVersion ?? "?"} Installer: {context.InstallerVersion ?? "?"}";
|
|
if (!string.IsNullOrEmpty(context.LatestVersion))
|
|
label += $" Latest: {context.LatestVersion}";
|
|
if (context.LatestTagUnparseable)
|
|
label += " (pre-release tag — auto-update disabled)";
|
|
VersionLabel = label;
|
|
|
|
_ = LoadAllAsync();
|
|
}
|
|
|
|
private async Task LoadAllAsync()
|
|
{
|
|
foreach (var page in Pages)
|
|
await page.LoadAsync();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task Save()
|
|
{
|
|
foreach (var page in Pages)
|
|
{
|
|
if (!page.Validate())
|
|
{
|
|
SelectedPage = page;
|
|
StatusMessage = $"Validation failed on {page.Title}. Please fix the errors.";
|
|
IsStatusError = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
foreach (var page in Pages)
|
|
await page.ApplyAsync();
|
|
|
|
var workerCfg = new InstallerWorkerConfig
|
|
{
|
|
DbPath = _context.DbPath,
|
|
SandboxRoot = _context.SandboxRoot,
|
|
LogRoot = _context.LogRoot,
|
|
WorktreeRootStrategy = _context.WorktreeRootStrategy,
|
|
CentralWorktreeRoot = _context.CentralWorktreeRoot,
|
|
QueueBackstopIntervalMs = _context.QueueBackstopIntervalMs,
|
|
SignalRPort = _context.SignalRPort,
|
|
ClaudeBin = _context.ClaudeBin,
|
|
};
|
|
workerCfg.Save();
|
|
|
|
var uiCfg = new InstallerAppSettings
|
|
{
|
|
DbPath = _context.UiDbPath,
|
|
SignalRUrl = _context.SignalRUrl,
|
|
Language = _localizer.CurrentCode,
|
|
};
|
|
uiCfg.Save();
|
|
|
|
IsStatusError = false;
|
|
StatusMessage = "Settings saved.";
|
|
|
|
// Worker reads its config at process start, so changes only take effect after a restart.
|
|
var restart = MessageBox.Show(
|
|
"Restart the worker service now so the new settings take effect?",
|
|
"Restart Worker",
|
|
MessageBoxButton.YesNo,
|
|
MessageBoxImage.Question);
|
|
|
|
if (restart != MessageBoxResult.Yes)
|
|
{
|
|
StatusMessage = "Settings saved. Restart the worker service manually to apply.";
|
|
return;
|
|
}
|
|
|
|
var progress = new Progress<string>(msg => StatusMessage = msg);
|
|
var stop = await _stopService.ExecuteAsync(_context, progress, CancellationToken.None);
|
|
if (!stop.Success)
|
|
{
|
|
StatusMessage = $"Saved, but worker stop failed: {stop.ErrorMessage}";
|
|
IsStatusError = true;
|
|
return;
|
|
}
|
|
var start = await _startService.ExecuteAsync(_context, progress, CancellationToken.None);
|
|
if (!start.Success)
|
|
{
|
|
StatusMessage = $"Saved, but worker start failed: {start.ErrorMessage}";
|
|
IsStatusError = true;
|
|
return;
|
|
}
|
|
|
|
StatusMessage = "Settings saved. Worker restarted.";
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task Repair()
|
|
{
|
|
var confirm = MessageBox.Show(
|
|
"Re-download and reinstall the ClaudeDo binaries? Your config and database are NOT affected.",
|
|
"Repair ClaudeDo",
|
|
MessageBoxButton.OKCancel,
|
|
MessageBoxImage.Question);
|
|
|
|
if (confirm != MessageBoxResult.OK) return;
|
|
|
|
StatusMessage = "Repairing...";
|
|
IsStatusError = false;
|
|
|
|
var progress = new Progress<string>(msg => StatusMessage = msg);
|
|
var steps = new IInstallStep[] { _stopService, _downloadStep, _registerAutostart, _startService };
|
|
|
|
foreach (var step in steps)
|
|
{
|
|
var r = await step.ExecuteAsync(_context, progress, CancellationToken.None);
|
|
if (!r.Success)
|
|
{
|
|
StatusMessage = $"{step.Name} failed: {r.ErrorMessage}";
|
|
IsStatusError = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
StatusMessage = "Repair complete.";
|
|
}
|
|
|
|
[RelayCommand]
|
|
private async Task Uninstall()
|
|
{
|
|
var dataNote = RemoveAppData
|
|
? "This will remove ClaudeDo AND delete all of your tasks, configuration, and database.\n\nContinue?"
|
|
: "This will remove ClaudeDo. Your tasks, configuration, and database in ~/.todo-app will be kept.\n\nContinue?";
|
|
|
|
var confirm = MessageBox.Show(
|
|
dataNote,
|
|
"Uninstall ClaudeDo",
|
|
MessageBoxButton.YesNo,
|
|
MessageBoxImage.Warning);
|
|
|
|
if (confirm != MessageBoxResult.Yes) return;
|
|
|
|
var progress = new Progress<string>(msg => StatusMessage = msg);
|
|
var r = await _uninstallRunner.RunAsync(RemoveAppData, progress, CancellationToken.None);
|
|
|
|
if (!r.Success)
|
|
{
|
|
StatusMessage = $"Uninstall failed: {r.ErrorMessage}";
|
|
IsStatusError = true;
|
|
return;
|
|
}
|
|
|
|
MessageBox.Show("ClaudeDo has been removed.", "Uninstall complete",
|
|
MessageBoxButton.OK, MessageBoxImage.Information);
|
|
Application.Current.Shutdown();
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void Close() => Application.Current.Shutdown();
|
|
}
|