From 4759480aaad6bd86a5639bcc77238ca1b82a8632 Mon Sep 17 00:00:00 2001 From: JT Date: Wed, 23 Aug 2023 00:16:05 -0700 Subject: [PATCH 01/18] added description to SelectModelVersionDialog --- .../DesignData/DesignData.cs | 6 +++- .../StabilityMatrix.Avalonia.csproj | 1 + .../CheckpointBrowserCardViewModel.cs | 28 ++++++++++++++++- .../Dialogs/SelectModelVersionViewModel.cs | 2 ++ .../Dialogs/SelectModelVersionDialog.axaml | 31 +++++++++++++------ 5 files changed, 57 insertions(+), 11 deletions(-) diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index 2aa2ab9f..b151dd79 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -325,7 +325,9 @@ public static class DesignData new() { Name = "BB95 Furry Mix", - Description = "v1.0.0", + Description = @"Introducing SnoutMix +A Mix of non-Furry and Furry models such as Furtastic and BB95Furry to create a great variety of anthro AI generation options, but bringing out more detail, still giving a lot of freedom to customise the human aspects, and having great backgrounds, with a focus on something more realistic. Works well with realistic character loras. +The gallery images are often inpainted, but you will get something very similar if copying their data directly. They are inpainted using the same model, therefore all results are possible without anything custom/hidden-away. Controlnet Tiled is applied to enhance them further afterwards. Gallery images were made with same model but before it was renamed", BaseModel = "SD 1.5", Files = new List { @@ -363,6 +365,8 @@ public static class DesignData // Sample data for dialogs vm.Versions = new List {sampleViewModel}; + vm.Title = sampleCivitVersions[0].Name; + vm.Description = sampleCivitVersions[0].Description; vm.SelectedVersionViewModel = sampleViewModel; }); diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj index ca4fc938..eae7da28 100644 --- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj +++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj @@ -21,6 +21,7 @@ + diff --git a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs index e9b2bd86..2e5a7b0c 100644 --- a/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/CheckpointBrowser/CheckpointBrowserCardViewModel.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Notifications; @@ -170,10 +171,15 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel IsSecondaryButtonEnabled = false, IsFooterVisible = false, MaxDialogWidth = 750, + MaxDialogHeight = 850, }; - + + var prunedDescription = PruneDescription(model); + var viewModel = dialogFactory.Get(); viewModel.Dialog = dialog; + viewModel.Title = model.Name; + viewModel.Description = prunedDescription; viewModel.Versions = versions.Select(version => new ModelVersionViewModel( settingsManager.Settings.InstalledModelHashes ?? new HashSet(), version)) @@ -199,6 +205,23 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel await DoImport(model, selectedVersion, selectedFile); } + private static string PruneDescription(CivitModel model) + { + var prunedDescription = + model.Description + .Replace("
", $"{Environment.NewLine}{Environment.NewLine}") + .Replace("
", $"{Environment.NewLine}{Environment.NewLine}") + .Replace("

", $"{Environment.NewLine}{Environment.NewLine}") + .Replace("", $"{Environment.NewLine}{Environment.NewLine}") + .Replace("", $"{Environment.NewLine}{Environment.NewLine}") + .Replace("", $"{Environment.NewLine}{Environment.NewLine}") + .Replace("", $"{Environment.NewLine}{Environment.NewLine}") + .Replace("", $"{Environment.NewLine}{Environment.NewLine}") + .Replace("", $"{Environment.NewLine}{Environment.NewLine}"); + prunedDescription = HtmlRegex().Replace(prunedDescription, string.Empty); + return prunedDescription; + } + private static async Task SaveCmInfo( CivitModel model, CivitModelVersion modelVersion, @@ -354,4 +377,7 @@ public partial class CheckpointBrowserCardViewModel : Base.ProgressViewModel IsImporting = false; }); } + + [GeneratedRegex("<[^>]+>")] + private static partial Regex HtmlRegex(); } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs index 082859db..defbcebd 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs @@ -18,6 +18,8 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase public required ContentDialog Dialog { get; set; } public required IReadOnlyList Versions { get; set; } + public required string Description { get; set; } + public required string Title { get; set; } [ObservableProperty] private Bitmap? previewImage; [ObservableProperty] private ModelVersionViewModel? selectedVersionViewModel; diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml index d7eb4c9f..98ac9108 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/SelectModelVersionDialog.axaml @@ -6,7 +6,7 @@ xmlns:api="clr-namespace:StabilityMatrix.Core.Models.Api;assembly=StabilityMatrix.Core" xmlns:dialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs" xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData" - mc:Ignorable="d" d:DesignWidth="700" d:DesignHeight="450" + mc:Ignorable="d" d:DesignWidth="700" d:DesignHeight="850" x:DataType="dialogs:SelectModelVersionViewModel" xmlns:icons="https://github.com/projektanker/icons.avalonia" xmlns:fluentAvalonia="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia" @@ -19,16 +19,29 @@ RowDefinitions="*,Auto" ColumnDefinitions="*,Auto,*"> - - + + + + + + @@ -78,7 +91,7 @@ + Margin="0,4,0,4"> Date: Wed, 23 Aug 2023 17:48:56 -0700 Subject: [PATCH 02/18] Adds ROCm check/torch install for ComfyUI --- StabilityMatrix.Core/Helper/HardwareHelper.cs | 8 ++++---- StabilityMatrix.Core/Models/Packages/ComfyUI.cs | 10 +++++++++- StabilityMatrix.Core/Python/PyVenvRunner.cs | 5 +++++ 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/StabilityMatrix.Core/Helper/HardwareHelper.cs b/StabilityMatrix.Core/Helper/HardwareHelper.cs index eaef9df8..50ccc1ff 100644 --- a/StabilityMatrix.Core/Helper/HardwareHelper.cs +++ b/StabilityMatrix.Core/Helper/HardwareHelper.cs @@ -125,13 +125,13 @@ public static partial class HardwareHelper } // Set ROCm for default if AMD and Linux - public static bool PreferRocm() => !HardwareHelper.HasNvidiaGpu() - && HardwareHelper.HasAmdGpu() + public static bool PreferRocm() => !HasNvidiaGpu() + && HasAmdGpu() && Compat.IsLinux; // Set DirectML for default if AMD and Windows - public static bool PreferDirectML() => !HardwareHelper.HasNvidiaGpu() - && HardwareHelper.HasAmdGpu() + public static bool PreferDirectML() => !HasNvidiaGpu() + && HasAmdGpu() && Compat.IsWindows; } diff --git a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs index 8a172284..d9c73f20 100644 --- a/StabilityMatrix.Core/Models/Packages/ComfyUI.cs +++ b/StabilityMatrix.Core/Models/Packages/ComfyUI.cs @@ -126,11 +126,19 @@ public class ComfyUI : BaseGitPackage Logger.Info("Installing xformers..."); await venvRunner.PipInstall("xformers", OnConsoleOutput).ConfigureAwait(false); } + else if (HardwareHelper.PreferRocm()) + { + progress?.Report(new ProgressReport(-1, "Installing PyTorch for ROCm", isIndeterminate: true)); + await venvRunner + .PipInstall(PyVenvRunner.TorchPipInstallArgsRocmStable, OnConsoleOutput) + .ConfigureAwait(false); + } else { progress?.Report(new ProgressReport(-1, "Installing PyTorch for CPU", isIndeterminate: true)); Logger.Info("Starting torch install (CPU)..."); - await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsCpu, OnConsoleOutput).ConfigureAwait(false); + await venvRunner.PipInstall(PyVenvRunner.TorchPipInstallArgsCpu, OnConsoleOutput) + .ConfigureAwait(false); } // Install requirements file diff --git a/StabilityMatrix.Core/Python/PyVenvRunner.cs b/StabilityMatrix.Core/Python/PyVenvRunner.cs index e50de47c..a6d792af 100644 --- a/StabilityMatrix.Core/Python/PyVenvRunner.cs +++ b/StabilityMatrix.Core/Python/PyVenvRunner.cs @@ -24,6 +24,11 @@ public class PyVenvRunner : IDisposable, IAsyncDisposable public const string TorchPipInstallArgsCpu = "torch torchvision torchaudio"; public const string TorchPipInstallArgsDirectML = "torch-directml"; + public const string TorchPipInstallArgsRocmStable = + "torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/rocm5.4.2"; + public const string TorchPipInstallArgsRocmNightly = + "--pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/rocm5.6"; + /// /// Relative path to the site-packages folder from the venv root. /// This is platform specific. From c90a24e6f36a655108d7be93ea3f677723044358 Mon Sep 17 00:00:00 2001 From: JT Date: Fri, 25 Aug 2023 14:59:14 -0700 Subject: [PATCH 03/18] Rearranged model version dialog & added model description, also added ROCm options for most packages --- .../Controls/BetterImage.cs | 63 ++++++++---- .../Models/ImageSource.cs | 85 ++++++++++++++++ .../Dialogs/SelectDataDirectoryViewModel.cs | 21 +++- .../Dialogs/SelectModelVersionViewModel.cs | 57 ++++++++--- .../ViewModels/ProgressManagerViewModel.cs | 1 + .../Dialogs/SelectDataDirectoryDialog.axaml | 8 ++ .../Dialogs/SelectModelVersionDialog.axaml | 99 ++++++++++++++----- .../Models/Packages/A3WebUI.cs | 18 ++++ .../Models/Packages/ComfyUI.cs | 2 +- .../Models/Packages/InvokeAI.cs | 2 +- StabilityMatrix.Core/Python/PyVenvRunner.cs | 10 +- 11 files changed, 304 insertions(+), 62 deletions(-) create mode 100644 StabilityMatrix.Avalonia/Models/ImageSource.cs diff --git a/StabilityMatrix.Avalonia/Controls/BetterImage.cs b/StabilityMatrix.Avalonia/Controls/BetterImage.cs index 8eb77eee..09405ca7 100644 --- a/StabilityMatrix.Avalonia/Controls/BetterImage.cs +++ b/StabilityMatrix.Avalonia/Controls/BetterImage.cs @@ -1,8 +1,10 @@ -using Avalonia; +using System; +using Avalonia; using Avalonia.Automation; using Avalonia.Automation.Peers; using Avalonia.Controls; using Avalonia.Controls.Automation.Peers; +using Avalonia.Layout; using Avalonia.Media; using Avalonia.Metadata; @@ -77,25 +79,48 @@ public class BetterImage : Control { var source = Source; - if (source != null && Bounds.Width > 0 && Bounds.Height > 0) + if (source == null || Bounds is not {Width: > 0, Height: > 0}) return; + + var viewPort = new Rect(Bounds.Size); + var sourceSize = source.Size; + + var scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection); + var scaledSize = sourceSize * scale; + + // Calculate starting points for dest + var destX = HorizontalAlignment switch { - Rect viewPort = new Rect(Bounds.Size); - Size sourceSize = source.Size; - - Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection); - Size scaledSize = sourceSize * scale; - Rect destRect = viewPort - .CenterRect(new Rect(scaledSize)) - .WithX(0) - .WithY(0) - .Intersect(viewPort); - Rect sourceRect = new Rect(sourceSize) - .CenterRect(new Rect(destRect.Size / scale)) - .WithX(0) - .WithY(0); - - context.DrawImage(source, sourceRect, destRect); - } + HorizontalAlignment.Left => 0, + HorizontalAlignment.Center => (int) (viewPort.Width - scaledSize.Width) / 2, + HorizontalAlignment.Right => (int) (viewPort.Width - scaledSize.Width), + // Stretch is default, use center + HorizontalAlignment.Stretch => (int) (viewPort.Width - scaledSize.Width) / 2, + _ => throw new ArgumentException(nameof(HorizontalAlignment)) + }; + + var destRect = viewPort + .CenterRect(new Rect(scaledSize)) + .WithX(destX) + .WithY(0) + .Intersect(viewPort); + var destRectUnscaledSize = destRect.Size / scale; + + var sourceX = HorizontalAlignment switch + { + HorizontalAlignment.Left => 0, + HorizontalAlignment.Center => (int) (sourceSize - destRectUnscaledSize).Width / 2, + HorizontalAlignment.Right => (int) (sourceSize - destRectUnscaledSize).Width, + // Stretch is default, use center + HorizontalAlignment.Stretch => (int) (sourceSize - destRectUnscaledSize).Width / 2, + _ => throw new ArgumentException(nameof(HorizontalAlignment)) + }; + + var sourceRect = new Rect(sourceSize) + .CenterRect(new Rect(destRect.Size / scale)) + .WithX(sourceX) + .WithY(0); + + context.DrawImage(source, sourceRect, destRect); } /// diff --git a/StabilityMatrix.Avalonia/Models/ImageSource.cs b/StabilityMatrix.Avalonia/Models/ImageSource.cs new file mode 100644 index 00000000..c4927ad5 --- /dev/null +++ b/StabilityMatrix.Avalonia/Models/ImageSource.cs @@ -0,0 +1,85 @@ +using System; +using System.Threading.Tasks; +using AsyncImageLoader; +using Avalonia.Media.Imaging; +using StabilityMatrix.Core.Models.FileInterfaces; + +namespace StabilityMatrix.Avalonia.Models; + +public record ImageSource : IDisposable +{ + /// + /// Local file path + /// + public FilePath? LocalFile { get; init; } + + /// + /// Remote URL + /// + public Uri? RemoteUrl { get; init; } + + /// + /// Bitmap + /// + public Bitmap? Bitmap { get; init; } + + public ImageSource(FilePath localFile) + { + LocalFile = localFile; + } + + public ImageSource(Uri remoteUrl) + { + RemoteUrl = remoteUrl; + } + + public Task BitmapAsync => GetBitmapAsync(); + + /// + /// Get the bitmap + /// + public async Task GetBitmapAsync() + { + if (Bitmap is not null) return Bitmap; + + var loader = ImageLoader.AsyncImageLoader; + + // Use local file path if available, otherwise remote URL + var path = LocalFile?.FullPath ?? RemoteUrl?.ToString(); + + if (path is null) return null; + + // Load the image + return await loader.ProvideImageAsync(path).ConfigureAwait(false); + } + + /// + /// Clears the cached bitmap + /// + protected virtual void Dispose(bool disposing) + { + if (!disposing) return; + + Bitmap?.Dispose(); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + public override string ToString() + { + return LocalFile?.FullPath ?? RemoteUrl?.ToString() ?? ""; + } + + /// + /// Implicit conversion to string for async image loader. + /// Resolves with the local file path if available, otherwise the remote URL. + /// Otherwise returns null. + /// + public static implicit operator string(ImageSource imageSource) => imageSource.ToString(); +} diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs index 2a680aaf..25e8928e 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectDataDirectoryViewModel.cs @@ -30,6 +30,8 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase private const string InvalidDirectoryText = "Directory must be empty or have a valid settings.json file"; private const string NotEnoughFreeSpaceText = "Not enough free space on the selected drive"; + private const string FatWarningText = + "FAT32 / exFAT drives are not supported at this time"; [ObservableProperty] private string dataDirectory = DefaultInstallLocation; [ObservableProperty] private bool isPortableMode; @@ -37,6 +39,7 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase [ObservableProperty] private string directoryStatusText = string.Empty; [ObservableProperty] private bool isStatusBadgeVisible; [ObservableProperty] private bool isDirectoryValid; + [ObservableProperty] private bool showFatWarning; public RefreshBadgeViewModel ValidatorRefreshBadge { get; } = new() { @@ -67,6 +70,8 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase private async Task ValidateDataDirectory() { await using var delay = new MinimumDelay(100, 200); + + ShowFatWarning = IsDriveFat(DataDirectory); // Doesn't exist, this is fine as a new install, hide badge if (!Directory.Exists(DataDirectory)) @@ -84,7 +89,7 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase try { var jsonText = await File.ReadAllTextAsync(settingsPath); - var _ = JsonSerializer.Deserialize(jsonText, new JsonSerializerOptions + JsonSerializer.Deserialize(jsonText, new JsonSerializerOptions { Converters = { new JsonStringEnumConverter() } }); @@ -152,4 +157,18 @@ public partial class SelectDataDirectoryViewModel : ContentDialogViewModelBase { DataDirectory = value ? Compat.AppCurrentDir + "Data" : DefaultInstallLocation; } + + private bool IsDriveFat(string path) + { + try + { + var drive = new DriveInfo(Path.GetPathRoot(path)); + return drive.DriveFormat.Contains("FAT", StringComparison.OrdinalIgnoreCase); + } + catch (Exception e) + { + Logger.Warn(e, "Error checking drive FATness"); + return false; + } + } } diff --git a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs index defbcebd..4d643c26 100644 --- a/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/Dialogs/SelectModelVersionViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using Avalonia.Media.Imaging; @@ -7,6 +8,7 @@ using Avalonia.Platform; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using FluentAvalonia.UI.Controls; +using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Core.Services; namespace StabilityMatrix.Avalonia.ViewModels.Dialogs; @@ -25,6 +27,15 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase [ObservableProperty] private ModelVersionViewModel? selectedVersionViewModel; [ObservableProperty] private CivitFileViewModel? selectedFile; [ObservableProperty] private bool isImportEnabled; + [ObservableProperty] private ObservableCollection imageUrls = new(); + [ObservableProperty] private bool canGoToNextImage; + [ObservableProperty] private bool canGoToPreviousImage; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(DisplayedPageNumber))] + private int selectedImageIndex; + + public int DisplayedPageNumber => SelectedImageIndex + 1; public SelectModelVersionViewModel(ISettingsManager settingsManager, IDownloadService downloadService) @@ -36,18 +47,31 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase public override void OnLoaded() { SelectedVersionViewModel = Versions[0]; + CanGoToNextImage = true; } partial void OnSelectedVersionViewModelChanged(ModelVersionViewModel? value) { var nsfwEnabled = settingsManager.Settings.ModelBrowserNsfwEnabled; - var firstImageUrl = value?.ModelVersion?.Images?.FirstOrDefault( - img => nsfwEnabled || img.Nsfw == "None")?.Url; + var allImages = value?.ModelVersion?.Images?.Where( + img => nsfwEnabled || img.Nsfw == "None")?.Select(x => new ImageSource(x.Url)).ToList(); + + if (allImages == null || !allImages.Any()) + { + allImages = new List {new(Assets.NoImage)}; + CanGoToNextImage = false; + } + else + { + CanGoToNextImage = allImages.Count > 1; + } - Dispatcher.UIThread.InvokeAsync(async () => + Dispatcher.UIThread.Post(() => { - SelectedFile = value?.CivitFileViewModels.FirstOrDefault(); - await UpdateImage(firstImageUrl); + CanGoToPreviousImage = false; + SelectedFile = SelectedVersionViewModel?.CivitFileViewModels.FirstOrDefault(); + ImageUrls = new ObservableCollection(allImages); + SelectedImageIndex = 0; }); } @@ -56,15 +80,6 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase IsImportEnabled = value?.CivitFile != null; } - private async Task UpdateImage(string? url = null) - { - var assetStream = string.IsNullOrWhiteSpace(url) - ? AssetLoader.Open(new Uri("avares://StabilityMatrix.Avalonia/Assets/noimage.png")) - : await downloadService.GetImageStreamFromUrl(url); - - PreviewImage = new Bitmap(assetStream); - } - public void Cancel() { Dialog.Hide(ContentDialogResult.Secondary); @@ -74,4 +89,18 @@ public partial class SelectModelVersionViewModel : ContentDialogViewModelBase { Dialog.Hide(ContentDialogResult.Primary); } + + public void PreviousImage() + { + if (SelectedImageIndex > 0) SelectedImageIndex--; + CanGoToPreviousImage = SelectedImageIndex > 0; + CanGoToNextImage = SelectedImageIndex < ImageUrls.Count - 1; + } + + public void NextImage() + { + if (SelectedImageIndex < ImageUrls.Count - 1) SelectedImageIndex++; + CanGoToPreviousImage = SelectedImageIndex > 0; + CanGoToNextImage = SelectedImageIndex < ImageUrls.Count - 1; + } } diff --git a/StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs b/StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs index 99ee8ebd..fa4ddf8f 100644 --- a/StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs +++ b/StabilityMatrix.Avalonia/ViewModels/ProgressManagerViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.IO; using System.Linq; using Avalonia.Collections; using Avalonia.Controls.Notifications; diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml index 2fe90df8..5483e137 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml @@ -55,6 +55,14 @@ Foreground="LightGray" FontSize="12" Margin="0,8,0,0" /> + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -79,6 +118,8 @@ @@ -119,9 +160,23 @@ + + + + + + -