diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index 7e74c808..57c82ddb 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -47,6 +47,7 @@ using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; using StabilityMatrix.Avalonia.ViewModels.Dialogs; using StabilityMatrix.Avalonia.ViewModels.PackageManager; +using StabilityMatrix.Avalonia.ViewModels.Progress; using StabilityMatrix.Avalonia.Views; using StabilityMatrix.Avalonia.Views.Dialogs; using StabilityMatrix.Core.Api; @@ -346,7 +347,6 @@ public sealed class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - //services.AddSingleton(); } private static IServiceCollection ConfigureServices() @@ -372,7 +372,7 @@ public sealed class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddTransient(); services.AddSingleton(); services.AddSingleton( diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index 39bb0342..b34ed33e 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -13,6 +13,7 @@ using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; using StabilityMatrix.Avalonia.ViewModels.Dialogs; +using StabilityMatrix.Avalonia.ViewModels.Progress; using StabilityMatrix.Core.Api; using StabilityMatrix.Core.Database; using StabilityMatrix.Core.Helper; @@ -276,6 +277,12 @@ public static class DesignData ) ), new MockDownloadProgressItemViewModel("Test File 2.exe"), + new PackageInstallProgressItemViewModel( + new PackageModificationRunner + { + CurrentProgress = new ProgressReport(0.5f, "Installing package...") + } + ) } ); diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/ConsoleProgressViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Base/ConsoleProgressViewModel.cs new file mode 100644 index 00000000..2f358f21 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/Base/ConsoleProgressViewModel.cs @@ -0,0 +1,11 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace StabilityMatrix.Avalonia.ViewModels.Base; + +public partial class ConsoleProgressViewModel : ProgressViewModel +{ + public ConsoleViewModel Console { get; } = new(); + + [ObservableProperty] + private bool closeWhenFinished; +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/ContentDialogProgressViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/ContentDialogProgressViewModelBase.cs index e329d085..b905bba6 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/ContentDialogProgressViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/ContentDialogProgressViewModelBase.cs @@ -3,7 +3,7 @@ using FluentAvalonia.UI.Controls; namespace StabilityMatrix.Avalonia.ViewModels.Base; -public class ContentDialogProgressViewModelBase : ProgressViewModel +public class ContentDialogProgressViewModelBase : ConsoleProgressViewModel { public event EventHandler? PrimaryButtonClick; public event EventHandler? SecondaryButtonClick; diff --git a/StabilityMatrix.Avalonia/ViewModels/Base/ProgressItemViewModelBase.cs b/StabilityMatrix.Avalonia/ViewModels/Base/ProgressItemViewModelBase.cs index a304da5b..e4e458d8 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Base/ProgressItemViewModelBase.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Base/ProgressItemViewModelBase.cs @@ -6,11 +6,16 @@ namespace StabilityMatrix.Avalonia.ViewModels.Base; public abstract partial class ProgressItemViewModelBase : ViewModelBase { - [ObservableProperty] private Guid id; - [ObservableProperty] private string? name; - [ObservableProperty] private bool failed; + [ObservableProperty] + private Guid id; + + [ObservableProperty] + private string? name; + + [ObservableProperty] + private bool failed; public virtual bool IsCompleted => Progress.Value >= 100 || Failed; - - public ProgressViewModel Progress { get; } = new(); + + public ContentDialogProgressViewModelBase Progress { get; } = new(); } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs index f41b6dea..68c9b96b 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/InstallerViewModel.cs @@ -215,6 +215,8 @@ public partial class InstallerViewModel : ContentDialogViewModelBase return Task.CompletedTask; } + var setPackageInstallingStep = new SetPackageInstallingStep(settingsManager, InstallName); + var installLocation = Path.Combine(settingsManager.LibraryDir, "Packages", InstallName); var prereqStep = new SetupPrerequisitesStep(prerequisiteHelper, pyRunner); @@ -270,6 +272,7 @@ public partial class InstallerViewModel : ContentDialogViewModelBase var steps = new List { + setPackageInstallingStep, prereqStep, downloadStep, installStep, diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageModificationDialogViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageModificationDialogViewModel.cs index 95e88c90..c2a65a4d 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageModificationDialogViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageModificationDialogViewModel.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Avalonia.Controls.Notifications; @@ -25,25 +26,43 @@ public class PackageModificationDialogViewModel : ContentDialogProgressViewModel this.packageModificationRunner = packageModificationRunner; this.notificationService = notificationService; this.steps = steps; + CloseWhenFinished = true; } - public ConsoleViewModel Console { get; } = new(); - public override async Task OnLoadedAsync() { + await Console.Clear(); + Console.Post(string.Join(Environment.NewLine, packageModificationRunner.ConsoleOutput)); + // idk why this is getting called twice if (!packageModificationRunner.IsRunning) { + EventManager.Instance.OnPackageInstallProgressAdded(packageModificationRunner); packageModificationRunner.ProgressChanged += PackageModificationRunnerOnProgressChanged; await packageModificationRunner.ExecuteSteps(steps.ToList()); + var packageName = string.Empty; + var addPackageStep = steps.FirstOrDefault(step => step is AddInstalledPackageStep); + if (addPackageStep != null) + { + addPackageStep + .GetType() + .GetProperty("newInstalledPackage") + ?.GetValue(addPackageStep, null); + } + notificationService.Show( "Package Install Completed", "Package install completed successfully.", NotificationType.Success ); - OnCloseButtonClick(); + EventManager.Instance.OnInstalledPackagesChanged(); + + if (CloseWhenFinished) + { + OnCloseButtonClick(); + } } } diff --git a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs index 66bb1a6d..20f81a94 100644 --- a/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/MainWindowViewModel.cs @@ -11,6 +11,7 @@ using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Dialogs; +using StabilityMatrix.Avalonia.ViewModels.Progress; using StabilityMatrix.Avalonia.Views; using StabilityMatrix.Avalonia.Views.Dialogs; using StabilityMatrix.Core.Attributes; @@ -27,13 +28,13 @@ public partial class MainWindowViewModel : ViewModelBase private readonly ITrackedDownloadService trackedDownloadService; private readonly IDiscordRichPresenceService discordRichPresenceService; public string Greeting => "Welcome to Avalonia!"; - + [ObservableProperty] private PageViewModelBase? currentPage; - - [ObservableProperty] + + [ObservableProperty] private object? selectedCategory; - + [ObservableProperty] private List pages = new(); @@ -44,16 +45,17 @@ public partial class MainWindowViewModel : ViewModelBase public UpdateViewModel UpdateViewModel { get; init; } public MainWindowViewModel( - ISettingsManager settingsManager, + ISettingsManager settingsManager, IDiscordRichPresenceService discordRichPresenceService, ServiceManager dialogFactory, - ITrackedDownloadService trackedDownloadService) + ITrackedDownloadService trackedDownloadService + ) { this.settingsManager = settingsManager; this.dialogFactory = dialogFactory; this.discordRichPresenceService = discordRichPresenceService; this.trackedDownloadService = trackedDownloadService; - + ProgressManagerViewModel = dialogFactory.Get(); UpdateViewModel = dialogFactory.Get(); } @@ -61,7 +63,7 @@ public partial class MainWindowViewModel : ViewModelBase public override void OnLoaded() { base.OnLoaded(); - + // Set only if null, since this may be called again when content dialogs open CurrentPage ??= Pages.FirstOrDefault(); SelectedCategory ??= Pages.FirstOrDefault(); @@ -72,24 +74,25 @@ public partial class MainWindowViewModel : ViewModelBase await base.OnLoadedAsync(); // Skip if design mode - if (Design.IsDesignMode) return; - + if (Design.IsDesignMode) + return; + if (!await EnsureDataDirectory()) { // False if user exited dialog, shutdown app App.Shutdown(); return; } - + // Initialize Discord Rich Presence (this needs LibraryDir so is set here) discordRichPresenceService.UpdateState(); - + // Load in-progress downloads ProgressManagerViewModel.AddDownloads(trackedDownloadService.Downloads); - + // Index checkpoints if we dont have Task.Run(() => settingsManager.IndexCheckpoints()).SafeFireAndForget(); - + if (!settingsManager.Settings.InstalledPackages.Any()) { var viewModel = dialogFactory.Get(); @@ -98,17 +101,15 @@ public partial class MainWindowViewModel : ViewModelBase IsPrimaryButtonEnabled = false, IsSecondaryButtonEnabled = false, IsFooterVisible = false, - Content = new OneClickInstallDialog - { - DataContext = viewModel - }, + Content = new OneClickInstallDialog { DataContext = viewModel }, }; EventManager.Instance.OneClickInstallFinished += (_, skipped) => { dialog.Hide(); - if (skipped) return; - + if (skipped) + return; + EventManager.Instance.OnTeachingTooltipNeeded(); }; @@ -125,24 +126,25 @@ public partial class MainWindowViewModel : ViewModelBase if (!settingsManager.TryFindLibrary()) { var result = await ShowSelectDataDirectoryDialog(); - if (!result) return false; + if (!result) + return false; } - + // Try to find library again, should be found now if (!settingsManager.TryFindLibrary()) { throw new Exception("Could not find library after setting path"); } - + // Tell LaunchPage to load any packages if they selected an existing directory EventManager.Instance.OnInstalledPackagesChanged(); - + // Check if there are old packages, if so show migration dialog // TODO: Migration dialog return true; } - + /// /// Shows the select data directory dialog. /// @@ -155,10 +157,7 @@ public partial class MainWindowViewModel : ViewModelBase IsPrimaryButtonEnabled = false, IsSecondaryButtonEnabled = false, IsFooterVisible = false, - Content = new SelectDataDirectoryDialog - { - DataContext = viewModel - } + Content = new SelectDataDirectoryDialog { DataContext = viewModel } }; var result = await dialog.ShowAsync(); @@ -191,10 +190,7 @@ public partial class MainWindowViewModel : ViewModelBase IsPrimaryButtonEnabled = false, IsSecondaryButtonEnabled = false, IsFooterVisible = false, - Content = new UpdateDialog - { - DataContext = viewModel - } + Content = new UpdateDialog { DataContext = viewModel } }; await dialog.ShowAsync(); diff --git a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs index b9deb2ae..10d937a0 100644 --- a/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs @@ -163,8 +163,6 @@ public partial class PackageManagerViewModel : PageViewModelBase await dialog.ShowAsync(); } - - await OnLoadedAsync(); } private IEnumerable IndexUnknownPackages() @@ -192,6 +190,11 @@ public partial class PackageManagerViewModel : PageViewModelBase continue; } + if (settingsManager.PackageInstallsInProgress.Contains(subDir.Name)) + { + continue; + } + yield return UnknownInstalledPackage.FromDirectoryName(subDir.Name); } } diff --git a/StabilityMatrix.Avalonia/ViewModels/DownloadProgressItemViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Progress/DownloadProgressItemViewModel.cs similarity index 94% rename from StabilityMatrix.Avalonia/ViewModels/DownloadProgressItemViewModel.cs rename to StabilityMatrix.Avalonia/ViewModels/Progress/DownloadProgressItemViewModel.cs index 7e5fee85..4281a5b5 100644 --- a/StabilityMatrix.Avalonia/ViewModels/DownloadProgressItemViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Progress/DownloadProgressItemViewModel.cs @@ -4,13 +4,13 @@ using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Core.Models; using StabilityMatrix.Core.Models.Progress; -namespace StabilityMatrix.Avalonia.ViewModels; +namespace StabilityMatrix.Avalonia.ViewModels.Progress; public class DownloadProgressItemViewModel : PausableProgressItemViewModelBase { private readonly TrackedDownload download; - public DownloadProgressItemViewModel(TrackedDownload download) + public DownloadProgressItemViewModel(TrackedDownload download) { this.download = download; diff --git a/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs new file mode 100644 index 00000000..1422a7f3 --- /dev/null +++ b/StabilityMatrix.Avalonia/ViewModels/Progress/PackageInstallProgressItemViewModel.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using FluentAvalonia.UI.Controls; +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Avalonia.Views.Dialogs; +using StabilityMatrix.Core.Helper; +using StabilityMatrix.Core.Models.PackageModification; +using StabilityMatrix.Core.Models.Progress; + +namespace StabilityMatrix.Avalonia.ViewModels.Progress; + +public partial class PackageInstallProgressItemViewModel : ProgressItemViewModelBase +{ + private readonly IPackageModificationRunner packageModificationRunner; + private BetterContentDialog? dialog; + + public PackageInstallProgressItemViewModel(IPackageModificationRunner packageModificationRunner) + { + this.packageModificationRunner = packageModificationRunner; + Id = packageModificationRunner.Id; + Name = packageModificationRunner.CurrentStep?.ProgressTitle; + Progress.Value = packageModificationRunner.CurrentProgress.Percentage; + Progress.Text = packageModificationRunner.ConsoleOutput.LastOrDefault(); + Progress.IsIndeterminate = packageModificationRunner.CurrentProgress.IsIndeterminate; + + Progress.Console.Post( + string.Join(Environment.NewLine, packageModificationRunner.ConsoleOutput) + ); + + packageModificationRunner.ProgressChanged += PackageModificationRunnerOnProgressChanged; + } + + private void PackageModificationRunnerOnProgressChanged(object? sender, ProgressReport e) + { + Progress.Value = e.Percentage; + Progress.Text = e.Message; + Progress.IsIndeterminate = e.IsIndeterminate; + Name = packageModificationRunner.CurrentStep?.ProgressTitle; + + if (string.IsNullOrWhiteSpace(e.Message) || e.Message.Equals("Downloading...")) + return; + + Progress.Console.PostLine(e.Message); + EventManager.Instance.OnScrollToBottomRequested(); + + if ( + e is { Message: not null, Percentage: >= 100 } + && e.Message.Contains("Package Install Complete") + && Progress.CloseWhenFinished + ) + { + EventManager.Instance.OnInstalledPackagesChanged(); + dialog?.Hide(); + } + } + + public async Task ShowProgressDialog() + { + Progress.CloseWhenFinished = true; + dialog = new BetterContentDialog + { + MaxDialogWidth = 900, + MinDialogWidth = 900, + DefaultButton = ContentDialogButton.Close, + IsPrimaryButtonEnabled = false, + IsSecondaryButtonEnabled = false, + IsFooterVisible = false, + Content = new PackageModificationDialog { DataContext = Progress } + }; + EventManager.Instance.OnToggleProgressFlyout(); + await dialog.ShowAsync(); + } +} diff --git a/StabilityMatrix.Avalonia/ViewModels/ProgressItemViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Progress/ProgressItemViewModel.cs similarity index 91% rename from StabilityMatrix.Avalonia/ViewModels/ProgressItemViewModel.cs rename to StabilityMatrix.Avalonia/ViewModels/Progress/ProgressItemViewModel.cs index 9d6cc55e..be10ebe0 100644 --- a/StabilityMatrix.Avalonia/ViewModels/ProgressItemViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Progress/ProgressItemViewModel.cs @@ -1,10 +1,8 @@ -using System; -using CommunityToolkit.Mvvm.ComponentModel; -using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models.Progress; -namespace StabilityMatrix.Avalonia.ViewModels; +namespace StabilityMatrix.Avalonia.ViewModels.Progress; public class ProgressItemViewModel : ProgressItemViewModelBase { diff --git a/StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Progress/ProgressManagerViewModel.cs similarity index 82% rename from StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs rename to StabilityMatrix.Avalonia/ViewModels/Progress/ProgressManagerViewModel.cs index 06dad5be..506d3126 100644 --- a/StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Progress/ProgressManagerViewModel.cs @@ -3,6 +3,7 @@ using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Notifications; using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; using FluentAvalonia.UI.Controls; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels.Base; @@ -10,12 +11,13 @@ using StabilityMatrix.Avalonia.Views; using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Helper; using StabilityMatrix.Core.Models; +using StabilityMatrix.Core.Models.PackageModification; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Services; using Symbol = FluentIcons.Common.Symbol; using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource; -namespace StabilityMatrix.Avalonia.ViewModels; +namespace StabilityMatrix.Avalonia.ViewModels.Progress; [View(typeof(ProgressManagerPage))] public partial class ProgressManagerViewModel : PageViewModelBase @@ -24,9 +26,10 @@ public partial class ProgressManagerViewModel : PageViewModelBase public override string Title => "Download Manager"; public override IconSource IconSource => new SymbolIconSource {Symbol = Symbol.ArrowCircleDown, IsFilled = true}; - public AvaloniaList ProgressItems { get; } = new(); + [ObservableProperty] private bool isOpen; + public ProgressManagerViewModel( ITrackedDownloadService trackedDownloadService, INotificationService notificationService) @@ -35,8 +38,15 @@ public partial class ProgressManagerViewModel : PageViewModelBase // Attach to the event trackedDownloadService.DownloadAdded += TrackedDownloadService_OnDownloadAdded; + EventManager.Instance.PackageInstallProgressAdded += InstanceOnPackageInstallProgressAdded; + EventManager.Instance.ToggleProgressFlyout += (_, _) => IsOpen = !IsOpen; } - + + private void InstanceOnPackageInstallProgressAdded(object? sender, IPackageModificationRunner runner) + { + AddPackageInstall(runner); + } + private void TrackedDownloadService_OnDownloadAdded(object? sender, TrackedDownload e) { var vm = new DownloadProgressItemViewModel(e); @@ -89,6 +99,17 @@ public partial class ProgressManagerViewModel : PageViewModelBase ProgressItems.Add(vm); } } + + private void AddPackageInstall(IPackageModificationRunner packageModificationRunner) + { + if (ProgressItems.Any(vm => vm.Id == packageModificationRunner.Id)) + { + return; + } + + var vm = new PackageInstallProgressItemViewModel(packageModificationRunner); + ProgressItems.Add(vm); + } private void ShowFailedNotification(string title, string message) { diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml index 3b8d8c37..fabaefd7 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/PackageModificationDialog.axaml @@ -5,10 +5,11 @@ xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls" xmlns:dialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs" xmlns:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit" + xmlns:base="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Base" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:DataType="dialogs:PackageModificationDialogViewModel" + x:DataType="base:ContentDialogProgressViewModelBase" x:Class="StabilityMatrix.Avalonia.Views.Dialogs.PackageModificationDialog"> - + - + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Core/Helper/EventManager.cs b/StabilityMatrix.Core/Helper/EventManager.cs index 025f7de5..8c90c373 100644 --- a/StabilityMatrix.Core/Helper/EventManager.cs +++ b/StabilityMatrix.Core/Helper/EventManager.cs @@ -1,4 +1,5 @@ using StabilityMatrix.Core.Models; +using StabilityMatrix.Core.Models.PackageModification; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Models.Update; @@ -10,11 +11,8 @@ public class EventManager { public static EventManager Instance { get; } = new(); - private EventManager() - { + private EventManager() { } - } - public event EventHandler? GlobalProgressChanged; public event EventHandler? InstalledPackagesChanged; public event EventHandler? OneClickInstallFinished; @@ -24,20 +22,42 @@ public class EventManager public event EventHandler? PackageLaunchRequested; public event EventHandler? ScrollToBottomRequested; public event EventHandler? ProgressChanged; - public event EventHandler? RunningPackageStatusChanged; - - public void OnGlobalProgressChanged(int progress) => GlobalProgressChanged?.Invoke(this, progress); - public void OnInstalledPackagesChanged() => InstalledPackagesChanged?.Invoke(this, EventArgs.Empty); - public void OnOneClickInstallFinished(bool skipped) => OneClickInstallFinished?.Invoke(this, skipped); + public event EventHandler? RunningPackageStatusChanged; + + public event EventHandler? PackageInstallProgressAdded; + public event EventHandler? ToggleProgressFlyout; + + public void OnGlobalProgressChanged(int progress) => + GlobalProgressChanged?.Invoke(this, progress); + + public void OnInstalledPackagesChanged() => + InstalledPackagesChanged?.Invoke(this, EventArgs.Empty); + + public void OnOneClickInstallFinished(bool skipped) => + OneClickInstallFinished?.Invoke(this, skipped); + public void OnTeachingTooltipNeeded() => TeachingTooltipNeeded?.Invoke(this, EventArgs.Empty); + public void OnDevModeSettingChanged(bool value) => DevModeSettingChanged?.Invoke(this, value); + public void OnUpdateAvailable(UpdateInfo args) => UpdateAvailable?.Invoke(this, args); + public void OnPackageLaunchRequested(Guid packageId) => PackageLaunchRequested?.Invoke(this, packageId); + public void OnScrollToBottomRequested() => ScrollToBottomRequested?.Invoke(this, EventArgs.Empty); - public void OnProgressChanged(ProgressItem progress) => - ProgressChanged?.Invoke(this, progress); + + public void OnProgressChanged(ProgressItem progress) => ProgressChanged?.Invoke(this, progress); + public void OnRunningPackageStatusChanged(PackagePair? currentPackagePair) => - RunningPackageStatusChanged?.Invoke(this, new RunningPackageStatusChangedEventArgs(currentPackagePair)); + RunningPackageStatusChanged?.Invoke( + this, + new RunningPackageStatusChangedEventArgs(currentPackagePair) + ); + + public void OnPackageInstallProgressAdded(IPackageModificationRunner runner) => + PackageInstallProgressAdded?.Invoke(this, runner); + + public void OnToggleProgressFlyout() => ToggleProgressFlyout?.Invoke(this, EventArgs.Empty); } diff --git a/StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs b/StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs index 1b431c16..434e06a0 100644 --- a/StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs +++ b/StabilityMatrix.Core/Models/PackageModification/AddInstalledPackageStep.cs @@ -19,10 +19,15 @@ public class AddInstalledPackageStep : IPackageStep public async Task ExecuteAsync(IProgress? progress = null) { + if (!string.IsNullOrWhiteSpace(newInstalledPackage.DisplayName)) + { + settingsManager.PackageInstallsInProgress.Remove(newInstalledPackage.DisplayName); + } + await using var transaction = settingsManager.BeginTransaction(); transaction.Settings.InstalledPackages.Add(newInstalledPackage); transaction.Settings.ActiveInstalledPackageId = newInstalledPackage.Id; } - public string ProgressTitle => "Finishing up..."; + public string ProgressTitle => $"{newInstalledPackage.DisplayName} Installed"; } diff --git a/StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs b/StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs index a39dfae2..c5d6d008 100644 --- a/StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs +++ b/StabilityMatrix.Core/Models/PackageModification/IPackageModificationRunner.cs @@ -9,4 +9,6 @@ public interface IPackageModificationRunner ProgressReport CurrentProgress { get; set; } IPackageStep? CurrentStep { get; set; } event EventHandler? ProgressChanged; + List ConsoleOutput { get; } + Guid Id { get; } } diff --git a/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs b/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs index 0f884fee..48990902 100644 --- a/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs +++ b/StabilityMatrix.Core/Models/PackageModification/PackageModificationRunner.cs @@ -6,9 +6,14 @@ public class PackageModificationRunner : IPackageModificationRunner { public async Task ExecuteSteps(IReadOnlyList steps) { - var progress = new Progress(report => + IProgress progress = new Progress(report => { CurrentProgress = report; + if (!string.IsNullOrWhiteSpace(report.Message)) + { + ConsoleOutput.Add(report.Message); + } + OnProgressChanged(report); }); @@ -19,12 +24,18 @@ public class PackageModificationRunner : IPackageModificationRunner await step.ExecuteAsync(progress).ConfigureAwait(false); } + progress.Report( + new ProgressReport(1f, message: "Package Install Complete", isIndeterminate: false) + ); + IsRunning = false; } public bool IsRunning { get; set; } public ProgressReport CurrentProgress { get; set; } public IPackageStep? CurrentStep { get; set; } + public List ConsoleOutput { get; } = new(); + public Guid Id { get; } = Guid.NewGuid(); public event EventHandler? ProgressChanged; diff --git a/StabilityMatrix.Core/Models/PackageModification/SetPackageInstallingStep.cs b/StabilityMatrix.Core/Models/PackageModification/SetPackageInstallingStep.cs new file mode 100644 index 00000000..9867fd50 --- /dev/null +++ b/StabilityMatrix.Core/Models/PackageModification/SetPackageInstallingStep.cs @@ -0,0 +1,24 @@ +using StabilityMatrix.Core.Models.Progress; +using StabilityMatrix.Core.Services; + +namespace StabilityMatrix.Core.Models.PackageModification; + +public class SetPackageInstallingStep : IPackageStep +{ + private readonly ISettingsManager settingsManager; + private readonly string packageName; + + public SetPackageInstallingStep(ISettingsManager settingsManager, string packageName) + { + this.settingsManager = settingsManager; + this.packageName = packageName; + } + + public Task ExecuteAsync(IProgress? progress = null) + { + settingsManager.PackageInstallsInProgress.Add(packageName); + return Task.CompletedTask; + } + + public string ProgressTitle => "Starting Package Installation"; +} diff --git a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs index c363c7c9..e98b367c 100644 --- a/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs +++ b/StabilityMatrix.Core/Models/Packages/VladAutomatic.cs @@ -101,6 +101,12 @@ public class VladAutomatic : BaseGitPackage Options = new() { "--lowvram", "--medvram" } }, new() + { + Name = "Auto-Launch Web UI", + Type = LaunchOptionType.Bool, + Options = new() { "--autolaunch" } + }, + new() { Name = "Force use of Intel OneAPI XPU backend", Type = LaunchOptionType.Bool, diff --git a/StabilityMatrix.Core/Models/Progress/ProgressItem.cs b/StabilityMatrix.Core/Models/Progress/ProgressItem.cs index 244c7d04..dd3d7551 100644 --- a/StabilityMatrix.Core/Models/Progress/ProgressItem.cs +++ b/StabilityMatrix.Core/Models/Progress/ProgressItem.cs @@ -1,3 +1,8 @@ namespace StabilityMatrix.Core.Models.Progress; -public record ProgressItem(Guid ProgressId, string Name, ProgressReport Progress, bool Failed = false); +public record ProgressItem( + Guid ProgressId, + string Name, + ProgressReport Progress, + bool Failed = false +); diff --git a/StabilityMatrix.Core/Services/ISettingsManager.cs b/StabilityMatrix.Core/Services/ISettingsManager.cs index aec86465..0bd1030a 100644 --- a/StabilityMatrix.Core/Services/ISettingsManager.cs +++ b/StabilityMatrix.Core/Services/ISettingsManager.cs @@ -15,6 +15,7 @@ public interface ISettingsManager string ModelsDirectory { get; } string DownloadsDirectory { get; } Settings Settings { get; } + List PackageInstallsInProgress { get; set; } event EventHandler? LibraryDirChanged; event EventHandler? SettingsPropertyChanged; diff --git a/StabilityMatrix.Core/Services/SettingsManager.cs b/StabilityMatrix.Core/Services/SettingsManager.cs index 076f1034..43fddf82 100644 --- a/StabilityMatrix.Core/Services/SettingsManager.cs +++ b/StabilityMatrix.Core/Services/SettingsManager.cs @@ -56,6 +56,7 @@ public class SettingsManager : ISettingsManager private string SettingsPath => Path.Combine(LibraryDir, "settings.json"); public string ModelsDirectory => Path.Combine(LibraryDir, "Models"); public string DownloadsDirectory => Path.Combine(LibraryDir, ".downloads"); + public List PackageInstallsInProgress { get; set; } = new(); public Settings Settings { get; private set; } = new();