Files
EngineeringSync/EngineeringSync.Setup/ViewModels/WizardViewModel.cs
EngineeringSync 04ae8a0aae Initial commit: EngineeringSync v1.0.0
Vollständige Implementierung des EngineeringSync-Middleware-Tools:
- Windows Service (Kestrel :5050) mit FileSystemWatcher + SignalR
- WPF Tray-App mit PendingChanges- und Projektverwaltungs-Fenster
- Setup-Wizard (8-Schritte-Installer)
- SQLite/EF Core Datenschicht (WAL-Modus)
- SHA-256-basiertes Debouncing (2s Fenster)
- Backup-System mit konfigurierbarer Aufbewahrung

Bugfixes & Verbesserungen:
- BUG-1: AppDbContext OnConfiguring invertierte Bedingung behoben
- BUG-2: Event-Handler-Leak in TrayApp (Fenster-Singleton-Pattern)
- BUG-3: ProjectConfigChanged SignalR-Signal in allen CRUD-Endpoints
- BUG-5: Rename-Sync löscht alte Datei im Simulations-Ordner
- BUG-6: Doppeltes Dispose von SignalR verhindert
- BUG-7: Registry-Deinstallation nur EngineeringSync-Eintrag entfernt
- S1: Path-Traversal-Schutz via SafeCombine() im SyncManager
- E1: FSW Buffer 64KB + automatischer Re-Scan bei Overflow
- E2: Retry-Logik (3x) für gesperrte Dateien mit exponentiellem Backoff
- E4: Channel.Writer.TryComplete() beim Shutdown
- C2: HubMethodNames-Konstanten statt Magic Strings
- E3: Pagination in Changes-API (page/pageSize Query-Parameter)
- A1: Fire-and-Forget mit try/catch + Logging

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 21:52:26 +01:00

136 lines
4.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.ObjectModel;
using System.Windows;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using EngineeringSync.Setup.Services;
using EngineeringSync.Setup.Views.Pages;
namespace EngineeringSync.Setup.ViewModels;
public partial class WizardViewModel : ObservableObject
{
public WizardState State { get; } = new();
[ObservableProperty] private WizardPageBase _currentPage = null!;
[ObservableProperty] private int _currentStepIndex;
[ObservableProperty] private bool _canGoBack;
[ObservableProperty] private bool _canGoNext = true;
[ObservableProperty] private bool _isLastStep;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShowNextButton))]
private bool _isInstalling;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ShowNextButton))]
private bool _isCompleted;
public bool ShowNextButton => !IsCompleted && !IsInstalling;
public ObservableCollection<WizardStep> Steps { get; } =
[
new("Willkommen", "\uE80F"), // Home icon
new("Installation", "\uE7B7"), // Folder icon
new("Erstes Projekt","\uE8B7"), // Link icon
new("Backup", "\uE72E"), // Save icon (NEU)
new("Optionen", "\uE713"), // Settings icon
new("Zusammenfassung","\uE8A9"), // List icon
new("Installation", "\uE896"), // Download icon
new("Fertig", "\uE930"), // Completed icon
];
private readonly List<Func<WizardPageBase>> _pageFactories;
private readonly InstallerService _installer;
public WizardViewModel()
{
_installer = new InstallerService(State);
_pageFactories =
[
() => new WelcomePage(this),
() => new InstallPathPage(this),
() => new FirstProjectPage(this),
() => new BackupOptionsPage(this), // NEU Index 3
() => new ServiceOptionsPage(this),
() => new SummaryPage(this),
() => new InstallingPage(this, _installer),
() => new CompletionPage(this),
];
NavigateTo(0);
}
public void NavigateTo(int index)
{
CurrentStepIndex = index;
CurrentPage = _pageFactories[index]();
Steps[index].IsActive = true;
for (int i = 0; i < Steps.Count; i++)
{
Steps[i].IsCompleted = i < index;
Steps[i].IsActive = i == index;
}
CanGoBack = index > 0 && index < Steps.Count - 1 && !IsInstalling;
IsLastStep = index == Steps.Count - 2; // "Zusammenfassung" ist letzter normaler Schritt
// IsInstalling wird NICHT hier gesetzt wird von InstallingPage kontrolliert
IsCompleted = index == Steps.Count - 1;
CanGoNext = !IsInstalling && !IsCompleted;
}
public void SetInstallingState(bool installing)
{
IsInstalling = installing;
// CanGoNext wird durch die Bedingung !IsInstalling && !IsCompleted neu evaluiert
CanGoNext = !IsInstalling && !IsCompleted;
// GoBackCommand und CancelCommand CanExecute Bedingungen neu evaluieren
GoBackCommand.NotifyCanExecuteChanged();
CancelCommand.NotifyCanExecuteChanged();
}
[RelayCommand(CanExecute = nameof(CanExecuteGoBack))]
private void GoBack() => NavigateTo(CurrentStepIndex - 1);
private bool CanExecuteGoBack => CanGoBack && !IsInstalling;
[RelayCommand]
private void GoNext()
{
if (!CurrentPage.Validate()) return;
if (CurrentStepIndex < _pageFactories.Count - 1)
NavigateTo(CurrentStepIndex + 1);
}
[RelayCommand(CanExecute = nameof(CanExecuteCancel))]
private void Cancel()
{
if (IsCompleted) return;
var result = MessageBox.Show(
"Setup wirklich abbrechen?",
"EngineeringSync Setup",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
if (result == MessageBoxResult.Yes)
Application.Current.Shutdown();
}
private bool CanExecuteCancel => !IsInstalling && !IsCompleted;
[RelayCommand]
private void Finish()
{
if (State.StartAfterInstall)
_installer.LaunchTrayApp();
Application.Current.Shutdown();
}
}
public partial class WizardStep(string title, string icon) : ObservableObject
{
public string Title { get; } = title;
public string Icon { get; } = icon;
[ObservableProperty] private bool _isActive;
[ObservableProperty] private bool _isCompleted;
}