feat(ui): complete Avalonia UI port with 7 main pages, tool settings & top MenuBar

This commit is contained in:
Richard
2026-08-10 10:48:34 +02:00
parent a0e18d2a57
commit b5bf97ae74
187 changed files with 20054 additions and 882 deletions
@@ -0,0 +1,103 @@
using System.Collections.ObjectModel;
using ClawdDotNet.App.Models;
using ClawdDotNet.App.Services;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
namespace ClawdDotNet.Desktop.ViewModels;
/// <summary>
/// Auswahl der Instanz beim Start — der erste Rückruf, den <c>AppHost</c> erfragt.
///
/// Die WinForms-Fassung mischte Datenzugriff, Spaltenformatierung und Meldungsfenster in
/// einem Formular. Hier steht nur, <em>was</em> geschieht; das <em>wie</em> es aussieht
/// liegt in der Ansicht, und Rückfragen laufen über <see cref="ConfirmError"/> statt über
/// einen direkten Dialogaufruf — so bleibt das Ansichtsmodell ohne Fensterbezug prüfbar.
/// </summary>
public sealed partial class InstancePickerViewModel : ViewModelBase
{
private readonly InstanceDirectoryManager _directories;
private readonly TaskCompletionSource<string?> _completion = new();
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(StartCommand))]
private InstanceInfo? _selected;
[ObservableProperty]
private string _newInstanceName = "";
/// <summary>Fehlertext für die Ansicht; leer heißt: alles in Ordnung.</summary>
[ObservableProperty]
private string _errorText = "";
public ObservableCollection<InstanceInfo> Instances { get; } = [];
/// <summary>Der gewählte Pfad, oder <c>null</c> bei Abbruch.</summary>
public Task<string?> Result => _completion.Task;
public string InstancesDirectory => _directories.InstancesDirectory;
public InstancePickerViewModel(InstanceDirectoryManager directories)
{
_directories = directories;
Refresh();
}
[RelayCommand]
private void Refresh()
{
Instances.Clear();
foreach (var instance in _directories.ListInstances())
Instances.Add(instance);
// Eine einzelne Instanz gleich vorwählen — der häufigste Fall, und ein Klick
// weniger bei jedem Start.
Selected ??= Instances.FirstOrDefault();
}
private bool CanStart => Selected is not null;
[RelayCommand(CanExecute = nameof(CanStart))]
private void Start() => _completion.TrySetResult(Selected!.FolderPath);
/// <summary>Abbruch — der Aufrufer beendet die Anwendung.</summary>
[RelayCommand]
private void Cancel() => _completion.TrySetResult(null);
[RelayCommand]
private void Create()
{
var name = NewInstanceName.Trim();
if (string.IsNullOrWhiteSpace(name))
{
ErrorText = "Bitte einen Instanznamen eingeben.";
return;
}
try
{
_directories.CreateInstance(name);
NewInstanceName = "";
ErrorText = "";
Refresh();
// Die frisch angelegte Instanz vorwählen — wer sie erstellt, will sie starten.
Selected = Instances.FirstOrDefault(i =>
string.Equals(i.InstanceName, name, StringComparison.Ordinal));
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException
or ArgumentException)
{
ErrorText = $"Instanz konnte nicht angelegt werden: {ex.Message}";
}
}
/// <summary>
/// Wird gesetzt, wenn ein Fehler von außen kommt (etwa eine unlesbare
/// Instanzkonfiguration), damit das Fenster offen bleiben und eine andere Wahl
/// zulassen kann.
/// </summary>
public void ConfirmError(string message) => ErrorText = message;
}