diff --git a/CHANGELOG.md b/CHANGELOG.md index 764ee032..c593229e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,37 @@ All notable changes to Stability Matrix will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html). +## v2.3.0 +### Added +- New installable Package - [Fooocus](https://github.com/lllyasviel/Fooocus) +- Added "Select New Data Directory" button to Settings +- Added "Skip to First/Last Page" buttons to the Model Browser +- Added VAE as a checkpoint category in the Model Browser +- Pause/Resume/Cancel buttons on downloads popup. Paused downloads persists and may be resumed after restarting the app +- Unknown Package installs in the Package directory will now show up with a button to import them +### Fixed +- Fixed issue where model version wouldn't be selected in the "All Versions" section of the Model Browser +- Improved Checkpoints page indexing performance +- Fixed issue where Checkpoints page may not show all checkpoints after clearing search filter +- Fixed issue where Checkpoints page may show incorrect checkpoints for the given filter after changing pages +- Fixed issue where Open Web UI button would try to load 0.0.0.0 addresses +- Fixed Dictionary error when launch arguments saved with duplicate arguments +- Fixed Launch arguments search not working +### Changed +- Changed update method for SD.Next to use the built-in upgrade functionality +- Model Browser navigation buttons are no longer disabled while changing pages + +## v2.2.1 +### Fixed +- Fixed SD.Next shared folders config not working with new config format, reverted to Junctions / Symlinks + ## v2.2.1 + ### Fixed - Fixed SD.Next shared folders config not working with new config format, reverted to Junctions / Symlinks ## v2.2.0 + ### Added - Added option to search by Base Model in the Model Browser - Animated page transitions diff --git a/README.md b/README.md index 5c2c9897..94eeac30 100644 --- a/README.md +++ b/README.md @@ -13,23 +13,27 @@ [sdnext]: https://github.com/vladmandic/automatic [voltaml]: https://github.com/VoltaML/voltaML-fast-stable-diffusion [invokeai]: https://github.com/invoke-ai/InvokeAI +[fooocus]: https://github.com/lllyasviel/Fooocus [civitai]: https://civitai.com/ Multi-Platform Package Manager for Stable Diffusion ### 🖱️ One click install and update for Stable Diffusion Web UI Packages -- Supports [Automatic 1111][auto1111], [Comfy UI][comfy], [SD.Next (Vladmandic)][sdnext], [VoltaML][voltaml], [InvokeAI][invokeai] +- Supports [Automatic 1111][auto1111], [Comfy UI][comfy], [SD.Next (Vladmandic)][sdnext], [VoltaML][voltaml], [InvokeAI][invokeai], and [Fooocus][fooocus] - Embedded Git and Python dependencies, with no need for either to be globally installed -- Fully Portable, move Stability Matrix's Data Directory to a new drive or computer at any time +- Fully portable - move Stability Matrix's Data Directory to a new drive or computer at any time + ### 🚀 Launcher with syntax highlighted terminal emulator, routed GUI input prompts - Launch arguments editor with predefined or custom options for each Package install -- Package environment variables +- Configurable Environment Variables + ### 🗃️ Checkpoint Manager, configured to be shared by all Package installs - Option to find CivitAI metadata and preview thumbnails for new local imports + ### ☁️ Model Browser to import from [CivitAI][civitai] - Automatically imports to the associated model folder depending on the model type -- Also downloads relavent metadata files and preview image +- Downloads relevant metadata files and preview image ![header](https://github.com/LykosAI/StabilityMatrix/assets/13956642/a9c5f925-8561-49ba-855b-1b7bf57d7c0d) @@ -48,17 +52,17 @@ Multi-Platform Package Manager for Stable Diffusion ### Model browser powered by [Civit AI][civitai] - Downloads new models, automatically uses the appropriate shared model directory -- Available immediately to all installed packages +- Pause and resume downloads, even after closing the app

### Shared model directory for all your packages - - Import local models by simple drag and drop +- Option to find CivitAI metadata and preview thumbnails for new local imports - Toggle visibility of categories like LoRA, VAE, CLIP, etc. -- For models imported from Civit AI, shows additional information like version, fp precision, and preview thumbnail on hover +

diff --git a/StabilityMatrix.Avalonia/App.axaml b/StabilityMatrix.Avalonia/App.axaml index 7d74ab58..3977db27 100644 --- a/StabilityMatrix.Avalonia/App.axaml +++ b/StabilityMatrix.Avalonia/App.axaml @@ -28,5 +28,6 @@ + diff --git a/StabilityMatrix.Avalonia/App.axaml.cs b/StabilityMatrix.Avalonia/App.axaml.cs index 382f7274..78baebba 100644 --- a/StabilityMatrix.Avalonia/App.axaml.cs +++ b/StabilityMatrix.Avalonia/App.axaml.cs @@ -34,11 +34,13 @@ using Sentry; using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.DesignData; using StabilityMatrix.Avalonia.Helpers; +using StabilityMatrix.Avalonia.Languages; using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; +using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; using StabilityMatrix.Avalonia.ViewModels.Dialogs; using StabilityMatrix.Avalonia.ViewModels.PackageManager; using StabilityMatrix.Avalonia.Views; @@ -57,8 +59,6 @@ using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Services; using StabilityMatrix.Core.Updater; using Application = Avalonia.Application; -using CheckpointFile = StabilityMatrix.Avalonia.ViewModels.CheckpointManager.CheckpointFile; -using CheckpointFolder = StabilityMatrix.Avalonia.ViewModels.CheckpointManager.CheckpointFolder; using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace StabilityMatrix.Avalonia; @@ -194,7 +194,12 @@ public sealed class App : Application Services = services.BuildServiceProvider(); var settingsManager = Services.GetRequiredService(); - settingsManager.TryFindLibrary(); + + if (settingsManager.TryFindLibrary()) + { + Cultures.TrySetSupportedCulture(settingsManager.Settings.Language); + } + Services.GetRequiredService().StartEventListener(); } @@ -204,13 +209,15 @@ public sealed class App : Application .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() .AddSingleton() .AddSingleton(); services.AddSingleton(provider => new MainWindowViewModel(provider.GetRequiredService(), provider.GetRequiredService(), - provider.GetRequiredService>()) + provider.GetRequiredService>(), + provider.GetRequiredService()) { Pages = { @@ -240,14 +247,16 @@ public sealed class App : Application services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + + // Dialog view models (singleton) services.AddSingleton(); services.AddSingleton(); // Other transients (usually sub view models) - services.AddTransient() - .AddTransient() - .AddTransient(); - + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); // Global progress @@ -273,7 +282,9 @@ public sealed class App : Application .Register(provider.GetRequiredService) .Register(provider.GetRequiredService) .Register(provider.GetRequiredService) - .Register(provider.GetRequiredService)); + .Register(provider.GetRequiredService) + .Register(provider.GetRequiredService) + ); } internal static void ConfigureViews(IServiceCollection services) @@ -285,6 +296,7 @@ public sealed class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); // Dialogs services.AddTransient(); @@ -292,6 +304,7 @@ public sealed class App : Application services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); // Controls services.AddTransient(); @@ -308,6 +321,7 @@ public sealed class App : Application services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } private static IServiceCollection ConfigureServices() @@ -333,6 +347,10 @@ public sealed class App : Application services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(provider => + (IDisposable) provider.GetRequiredService()); + // Rich presence services.AddSingleton(); services.AddSingleton(provider => diff --git a/StabilityMatrix.Avalonia/Controls/AutoGrid.cs b/StabilityMatrix.Avalonia/Controls/AutoGrid.cs new file mode 100644 index 00000000..df5c4735 --- /dev/null +++ b/StabilityMatrix.Avalonia/Controls/AutoGrid.cs @@ -0,0 +1,408 @@ +// Modified from https://github.com/AvaloniaUI/AvaloniaAutoGrid +/*The MIT License (MIT) + +Copyright (c) 2013 Charles Brown (carbonrobot) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.*/ + +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Data; +using Avalonia.Layout; + +namespace StabilityMatrix.Avalonia.Controls; + +/// +/// Defines a flexible grid area that consists of columns and rows. +/// Depending on the orientation, either the rows or the columns are auto-generated, +/// and the children's position is set according to their index. +/// +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] +public class AutoGrid : Grid +{ + /// + /// Gets or sets the child horizontal alignment. + /// + /// The child horizontal alignment. + [Category("Layout"), Description("Presets the horizontal alignment of all child controls")] + public HorizontalAlignment? ChildHorizontalAlignment + { + get => (HorizontalAlignment?)GetValue(ChildHorizontalAlignmentProperty); + set => SetValue(ChildHorizontalAlignmentProperty, value); + } + + /// + /// Gets or sets the child margin. + /// + /// The child margin. + [Category("Layout"), Description("Presets the margin of all child controls")] + public Thickness? ChildMargin + { + get => (Thickness?)GetValue(ChildMarginProperty); + set => SetValue(ChildMarginProperty, value); + } + + /// + /// Gets or sets the child vertical alignment. + /// + /// The child vertical alignment. + [Category("Layout"), Description("Presets the vertical alignment of all child controls")] + public VerticalAlignment? ChildVerticalAlignment + { + get => (VerticalAlignment?)GetValue(ChildVerticalAlignmentProperty); + set => SetValue(ChildVerticalAlignmentProperty, value); + } + + /// + /// Gets or sets the column count + /// + [Category("Layout"), Description("Defines a set number of columns")] + public int ColumnCount + { + get => (int)GetValue(ColumnCountProperty)!; + set => SetValue(ColumnCountProperty, value); + } + + /// + /// Gets or sets the fixed column width + /// + [Category("Layout"), Description("Presets the width of all columns set using the ColumnCount property")] + + public GridLength ColumnWidth + { + get => (GridLength)GetValue(ColumnWidthProperty)!; + set => SetValue(ColumnWidthProperty, value); + } + + /// + /// Gets or sets a value indicating whether the children are automatically indexed. + /// + /// The default is true. + /// Note that if children are already indexed, setting this property to false will not remove their indices. + /// + /// + [Category("Layout"), Description("Set to false to disable the auto layout functionality")] + public bool IsAutoIndexing + { + get => (bool)GetValue(IsAutoIndexingProperty)!; + set => SetValue(IsAutoIndexingProperty, value); + } + + /// + /// Gets or sets the orientation. + /// The default is Vertical. + /// + /// The orientation. + [Category("Layout"), Description("Defines the directionality of the autolayout. Use vertical for a column first layout, horizontal for a row first layout.")] + public Orientation Orientation + { + get => (Orientation)GetValue(OrientationProperty)!; + set => SetValue(OrientationProperty, value); + } + + /// + /// Gets or sets the number of rows + /// + [Category("Layout"), Description("Defines a set number of rows")] + public int RowCount + { + get => (int)GetValue(RowCountProperty)!; + set => SetValue(RowCountProperty, value); + } + + /// + /// Gets or sets the fixed row height + /// + [Category("Layout"), Description("Presets the height of all rows set using the RowCount property")] + public GridLength RowHeight + { + get => (GridLength)GetValue(RowHeightProperty)!; + set => SetValue(RowHeightProperty, value); + } + + /// + /// Handles the column count changed event + /// + public static void ColumnCountChanged(AvaloniaPropertyChangedEventArgs e) + { + if ((int)e.NewValue! < 0) + return; + + var grid = (AutoGrid)e.Sender; + + + // look for an existing column definition for the height + var width = grid.ColumnWidth; + if (!grid.IsSet(ColumnWidthProperty) && grid.ColumnDefinitions.Count > 0) + width = grid.ColumnDefinitions[0].Width; + + // clear and rebuild + grid.ColumnDefinitions.Clear(); + for (var i = 0; i < (int)e.NewValue; i++) + grid.ColumnDefinitions.Add( + new ColumnDefinition() { Width = width }); + } + + /// + /// Handle the fixed column width changed event + /// + public static void FixedColumnWidthChanged(AvaloniaPropertyChangedEventArgs e) + { + var grid = (AutoGrid)e.Sender; + + // add a default column if missing + if (grid.ColumnDefinitions.Count == 0) + grid.ColumnDefinitions.Add(new ColumnDefinition()); + + // set all existing columns to this width + foreach (var t in grid.ColumnDefinitions) + t.Width = (GridLength)e.NewValue!; + } + + /// + /// Handle the fixed row height changed event + /// + public static void FixedRowHeightChanged(AvaloniaPropertyChangedEventArgs e) + { + var grid = (AutoGrid)e.Sender; + + // add a default row if missing + if (grid.RowDefinitions.Count == 0) + grid.RowDefinitions.Add(new RowDefinition()); + + // set all existing rows to this height + foreach (var t in grid.RowDefinitions) + t.Height = (GridLength)e.NewValue!; + } + + /// + /// Handles the row count changed event + /// + public static void RowCountChanged(AvaloniaPropertyChangedEventArgs e) + { + if ((int)e.NewValue! < 0) + return; + + var grid = (AutoGrid)e.Sender; + + // look for an existing row to get the height + var height = grid.RowHeight; + if (!grid.IsSet(RowHeightProperty) && grid.RowDefinitions.Count > 0) + height = grid.RowDefinitions[0].Height; + + // clear and rebuild + grid.RowDefinitions.Clear(); + for (var i = 0; i < (int)e.NewValue; i++) + grid.RowDefinitions.Add( + new RowDefinition() { Height = height }); + } + + /// + /// Called when [child horizontal alignment changed]. + /// + private static void OnChildHorizontalAlignmentChanged(AvaloniaPropertyChangedEventArgs e) + { + var grid = (AutoGrid)e.Sender; + foreach (var child in grid.Children) + { + child.SetValue(HorizontalAlignmentProperty, + grid.ChildHorizontalAlignment ?? AvaloniaProperty.UnsetValue); + } + } + + /// + /// Called when [child layout changed]. + /// + private static void OnChildMarginChanged(AvaloniaPropertyChangedEventArgs e) + { + var grid = (AutoGrid)e.Sender; + foreach (var child in grid.Children) + { + child.SetValue(MarginProperty, grid.ChildMargin ?? AvaloniaProperty.UnsetValue); + } + } + + /// + /// Called when [child vertical alignment changed]. + /// + private static void OnChildVerticalAlignmentChanged(AvaloniaPropertyChangedEventArgs e) + { + var grid = (AutoGrid)e.Sender; + foreach (var child in grid.Children) + { + child.SetValue(VerticalAlignmentProperty, grid.ChildVerticalAlignment ?? AvaloniaProperty.UnsetValue); + } + } + + /// + /// Apply child margins and layout effects such as alignment + /// + private void ApplyChildLayout(Control child) + { + if (ChildMargin != null) + { + child.SetValue(MarginProperty, ChildMargin.Value, BindingPriority.Template); + } + if (ChildHorizontalAlignment != null) + { + child.SetValue(HorizontalAlignmentProperty, ChildHorizontalAlignment.Value, BindingPriority.Template); + } + if (ChildVerticalAlignment != null) + { + child.SetValue(VerticalAlignmentProperty, ChildVerticalAlignment.Value, BindingPriority.Template); + } + } + + /// + /// Clamp a value to its maximum. + /// + private int Clamp(int value, int max) + { + return (value > max) ? max : value; + } + + /// + /// Perform the grid layout of row and column indexes + /// + private void PerformLayout() + { + var fillRowFirst = Orientation == Orientation.Horizontal; + var rowCount = RowDefinitions.Count; + var colCount = ColumnDefinitions.Count; + + if (rowCount == 0 || colCount == 0) + return; + + var position = 0; + var skip = new bool[rowCount, colCount]; + foreach (var child in Children.OfType()) + { + var childIsCollapsed = !child.IsVisible; + if (IsAutoIndexing && !childIsCollapsed) + { + if (fillRowFirst) + { + var row = Clamp(position / colCount, rowCount - 1); + var col = Clamp(position % colCount, colCount - 1); + if (skip[row, col]) + { + position++; + row = (position / colCount); + col = (position % colCount); + } + + SetRow(child, row); + SetColumn(child, col); + position += GetColumnSpan(child); + + var offset = GetRowSpan(child) - 1; + while (offset > 0) + { + skip[row + offset--, col] = true; + } + } + else + { + var row = Clamp(position % rowCount, rowCount - 1); + var col = Clamp(position / rowCount, colCount - 1); + if (skip[row, col]) + { + position++; + row = position % rowCount; + col = position / rowCount; + } + + SetRow(child, row); + SetColumn(child, col); + position += GetRowSpan(child); + + var offset = GetColumnSpan(child) - 1; + while (offset > 0) + { + skip[row, col + offset--] = true; + } + } + } + + ApplyChildLayout(child); + } + } + + public static readonly AvaloniaProperty ChildHorizontalAlignmentProperty = + AvaloniaProperty.Register("ChildHorizontalAlignment"); + + public static readonly AvaloniaProperty ChildMarginProperty = + AvaloniaProperty.Register("ChildMargin"); + + public static readonly AvaloniaProperty ChildVerticalAlignmentProperty = + AvaloniaProperty.Register("ChildVerticalAlignment"); + + public static readonly AvaloniaProperty ColumnCountProperty = + AvaloniaProperty.RegisterAttached("ColumnCount", typeof(AutoGrid), 1); + + public static readonly AvaloniaProperty ColumnWidthProperty = + AvaloniaProperty.RegisterAttached("ColumnWidth", typeof(AutoGrid), GridLength.Auto); + + public static readonly AvaloniaProperty IsAutoIndexingProperty = + AvaloniaProperty.Register("IsAutoIndexing", true); + + public static readonly AvaloniaProperty OrientationProperty = + AvaloniaProperty.Register("Orientation", Orientation.Vertical); + + public static readonly AvaloniaProperty RowCountProperty = + AvaloniaProperty.RegisterAttached("RowCount", typeof(AutoGrid), 1); + + public static readonly AvaloniaProperty RowHeightProperty = + AvaloniaProperty.RegisterAttached("RowHeight", typeof(AutoGrid), GridLength.Auto); + + static AutoGrid() + { + AffectsMeasure(ChildHorizontalAlignmentProperty, ChildMarginProperty, + ChildVerticalAlignmentProperty, ColumnCountProperty, ColumnWidthProperty, IsAutoIndexingProperty, OrientationProperty, + RowHeightProperty); + + ChildHorizontalAlignmentProperty.Changed.Subscribe(OnChildHorizontalAlignmentChanged); + ChildMarginProperty.Changed.Subscribe(OnChildMarginChanged); + ChildVerticalAlignmentProperty.Changed.Subscribe(OnChildVerticalAlignmentChanged); + ColumnCountProperty.Changed.Subscribe(ColumnCountChanged); + RowCountProperty.Changed.Subscribe(RowCountChanged); + ColumnWidthProperty.Changed.Subscribe(FixedColumnWidthChanged); + RowHeightProperty.Changed.Subscribe(FixedRowHeightChanged); + } + + #region Overrides + + /// + /// Measures the children of a in anticipation of arranging them during the pass. + /// + /// Indicates an upper limit size that should not be exceeded. + /// + /// that represents the required size to arrange child content. + /// + protected override Size MeasureOverride(Size constraint) + { + PerformLayout(); + return base.MeasureOverride(constraint); + } + + #endregion Overrides +} diff --git a/StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs b/StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs index 33511dbc..732715a3 100644 --- a/StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs +++ b/StabilityMatrix.Avalonia/Controls/BetterContentDialog.cs @@ -132,6 +132,15 @@ public class BetterContentDialog : ContentDialog get => GetValue(MaxDialogHeightProperty); set => SetValue(MaxDialogHeightProperty, value); } + + public static readonly StyledProperty ContentMarginProperty = AvaloniaProperty.Register( + "ContentMargin"); + + public Thickness ContentMargin + { + get => GetValue(ContentMarginProperty); + set => SetValue(ContentMarginProperty, value); + } public BetterContentDialog() @@ -205,6 +214,18 @@ public class BetterContentDialog : ContentDialog TryBindButtons(); } + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + var background = e.NameScope.Find("BackgroundElement"); + if (background is not null) + { + background.Margin = ContentMargin; + } + } + private void OnLoaded(object? sender, RoutedEventArgs? e) { TryBindButtons(); diff --git a/StabilityMatrix.Avalonia/DesignData/DesignData.cs b/StabilityMatrix.Avalonia/DesignData/DesignData.cs index b0fc04ba..69b8d7f0 100644 --- a/StabilityMatrix.Avalonia/DesignData/DesignData.cs +++ b/StabilityMatrix.Avalonia/DesignData/DesignData.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using System.Net.Http; using Microsoft.Extensions.DependencyInjection; using StabilityMatrix.Avalonia.Models; @@ -12,8 +11,8 @@ using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.ViewModels; using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser; +using StabilityMatrix.Avalonia.ViewModels.CheckpointManager; using StabilityMatrix.Avalonia.ViewModels.Dialogs; -using StabilityMatrix.Avalonia.ViewModels.PackageManager; using StabilityMatrix.Core.Api; using StabilityMatrix.Core.Database; using StabilityMatrix.Core.Helper; @@ -25,8 +24,6 @@ using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Python; using StabilityMatrix.Core.Services; using StabilityMatrix.Core.Updater; -using CheckpointFile = StabilityMatrix.Avalonia.ViewModels.CheckpointManager.CheckpointFile; -using CheckpointFolder = StabilityMatrix.Avalonia.ViewModels.CheckpointManager.CheckpointFolder; namespace StabilityMatrix.Avalonia.DesignData; @@ -91,7 +88,8 @@ public static class DesignData .AddSingleton() .AddSingleton() .AddSingleton() - .AddSingleton(); + .AddSingleton() + .AddSingleton(); // Placeholder services that nobody should need during design time services @@ -192,6 +190,11 @@ public static class DesignData { Title = "StableDiffusion", DirectoryPath = "Packages/Lora/Subfolder", + }, + new(settingsManager, downloadService, modelFinder) + { + Title = "Lora", + DirectoryPath = "Packages/StableDiffusion/Subfolder", } }, CheckpointFiles = new AdvancedObservableList @@ -223,14 +226,42 @@ public static class DesignData }) }; - ProgressManagerViewModel.ProgressItems = new ObservableCollection + NewCheckpointsPageViewModel.AllCheckpoints = new ObservableCollection { - new(new ProgressItem(Guid.NewGuid(), "Test File.exe", - new ProgressReport(0.5f, "Downloading..."))), - new(new ProgressItem(Guid.NewGuid(), "Test File 2.uwu", - new ProgressReport(0.25f, "Extracting..."))) + new() + { + FilePath = "~/Models/StableDiffusion/electricity-light.safetensors", + Title = "Auroral Background", + PreviewImagePath = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/" + + "78fd2a0a-42b6-42b0-9815-81cb11bb3d05/00009-2423234823.jpeg", + ConnectedModel = new ConnectedModelInfo + { + VersionName = "Lightning Auroral", + BaseModel = "SD 1.5", + ModelName = "Auroral Background", + ModelType = CivitModelType.Model, + FileMetadata = new CivitFileMetadata + { + Format = CivitModelFormat.SafeTensor, + Fp = CivitModelFpType.fp16, + Size = CivitModelSize.pruned, + } + } + }, + new() + { + FilePath = "~/Models/Lora/model.safetensors", + Title = "Some model" + } }; + ProgressManagerViewModel.ProgressItems.AddRange(new ProgressItemViewModelBase[] + { + new ProgressItemViewModel(new ProgressItem(Guid.NewGuid(), "Test File.exe", + new ProgressReport(0.5f, "Downloading..."))), + new MockDownloadProgressItemViewModel("Test File 2.exe"), + }); + UpdateViewModel = Services.GetRequiredService(); UpdateViewModel.UpdateText = $"Stability Matrix v2.0.1 is now available! You currently have v2.0.0. Would you like to update now?"; @@ -262,10 +293,15 @@ public static class DesignData { var settings = Services.GetRequiredService(); var vm = Services.GetRequiredService(); - vm.Packages = new ObservableCollection( - settings.Settings.InstalledPackages.Select(p => - DialogFactory.Get(viewModel => viewModel.Package = p))); - vm.Packages.First().IsUpdateAvailable = true; + + vm.SetPackages(settings.Settings.InstalledPackages); + vm.SetUnknownPackages(new InstalledPackage[] + { + UnknownInstalledPackage.FromDirectoryName("sd-unknown"), + }); + + vm.PackageCards[0].IsUpdateAvailable = true; + return vm; } } @@ -273,6 +309,9 @@ public static class DesignData public static CheckpointsPageViewModel CheckpointsPageViewModel => Services.GetRequiredService(); + public static NewCheckpointsPageViewModel NewCheckpointsPageViewModel => + Services.GetRequiredService(); + public static SettingsViewModel SettingsViewModel => Services.GetRequiredService(); @@ -368,6 +407,9 @@ public static class DesignData }; }); + public static PackageImportViewModel PackageImportViewModel => + DialogFactory.Get(); + public static RefreshBadgeViewModel RefreshBadgeViewModel => new() { State = ProgressState.Success diff --git a/StabilityMatrix.Avalonia/DesignData/MockDownloadProgressItemViewModel.cs b/StabilityMatrix.Avalonia/DesignData/MockDownloadProgressItemViewModel.cs new file mode 100644 index 00000000..5f94c91d --- /dev/null +++ b/StabilityMatrix.Avalonia/DesignData/MockDownloadProgressItemViewModel.cs @@ -0,0 +1,65 @@ +using System.Threading; +using System.Threading.Tasks; +using StabilityMatrix.Avalonia.ViewModels.Base; +using StabilityMatrix.Core.Models.Progress; + +namespace StabilityMatrix.Avalonia.DesignData; + +public class MockDownloadProgressItemViewModel : PausableProgressItemViewModelBase +{ + private Task? dummyTask; + private CancellationTokenSource? cts; + + public MockDownloadProgressItemViewModel(string fileName) + { + Name = fileName; + Progress.Value = 5; + Progress.IsIndeterminate = false; + Progress.Text = "Downloading..."; + } + + /// + public override Task Cancel() + { + // Cancel the task that updates progress + cts?.Cancel(); + cts = null; + dummyTask = null; + + State = ProgressState.Cancelled; + Progress.Text = "Cancelled"; + return Task.CompletedTask; + } + + /// + public override Task Pause() + { + // Cancel the task that updates progress + cts?.Cancel(); + cts = null; + dummyTask = null; + + State = ProgressState.Inactive; + + return Task.CompletedTask; + } + + /// + public override Task Resume() + { + // Start a task that updates progress every 100ms + cts = new CancellationTokenSource(); + dummyTask = Task.Run(async () => + { + while (State != ProgressState.Success) + { + await Task.Delay(100, cts.Token); + Progress.Value += 1; + } + }, cts.Token); + + State = ProgressState.Working; + + return Task.CompletedTask; + } +} diff --git a/StabilityMatrix.Avalonia/DesignData/MockDownloadService.cs b/StabilityMatrix.Avalonia/DesignData/MockDownloadService.cs index 419d972e..f3b35519 100644 --- a/StabilityMatrix.Avalonia/DesignData/MockDownloadService.cs +++ b/StabilityMatrix.Avalonia/DesignData/MockDownloadService.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Threading; using System.Threading.Tasks; using StabilityMatrix.Core.Models.Progress; using StabilityMatrix.Core.Services; @@ -8,8 +9,16 @@ namespace StabilityMatrix.Avalonia.DesignData; public class MockDownloadService : IDownloadService { - public Task DownloadToFileAsync(string downloadUrl, string downloadPath, - IProgress? progress = null, string? httpClientName = null) + public Task DownloadToFileAsync(string downloadUrl, string downloadPath, IProgress? progress = null, + string? httpClientName = null, CancellationToken cancellationToken = default) + { + return Task.CompletedTask; + } + + /// + public Task ResumeDownloadToFileAsync(string downloadUrl, string downloadPath, long existingFileSize, + IProgress? progress = null, string? httpClientName = null, + CancellationToken cancellationToken = default) { return Task.CompletedTask; } diff --git a/StabilityMatrix.Avalonia/DesignData/MockTrackedDownloadService.cs b/StabilityMatrix.Avalonia/DesignData/MockTrackedDownloadService.cs new file mode 100644 index 00000000..4522bd2d --- /dev/null +++ b/StabilityMatrix.Avalonia/DesignData/MockTrackedDownloadService.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using StabilityMatrix.Core.Models; +using StabilityMatrix.Core.Models.FileInterfaces; +using StabilityMatrix.Core.Services; + +namespace StabilityMatrix.Avalonia.DesignData; + +public class MockTrackedDownloadService : ITrackedDownloadService +{ + /// + public IEnumerable Downloads => Array.Empty(); + + /// + public event EventHandler? DownloadAdded; + + /// + public TrackedDownload NewDownload(Uri downloadUrl, FilePath downloadPath) + { + throw new NotImplementedException(); + } +} diff --git a/StabilityMatrix.Avalonia/Extensions/DirectoryPathExtensions.cs b/StabilityMatrix.Avalonia/Extensions/DirectoryPathExtensions.cs new file mode 100644 index 00000000..508d0818 --- /dev/null +++ b/StabilityMatrix.Avalonia/Extensions/DirectoryPathExtensions.cs @@ -0,0 +1,91 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Polly; +using StabilityMatrix.Core.Models.FileInterfaces; + +namespace StabilityMatrix.Avalonia.Extensions; + +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] +public static class DirectoryPathExtensions +{ + /// + /// Deletes a directory and all of its contents recursively. + /// Uses Polly to retry the deletion if it fails, up to 5 times with an exponential backoff. + /// + public static Task DeleteVerboseAsync(this DirectoryPath directory, ILogger? logger = default) + { + var policy = Policy.Handle() + .WaitAndRetryAsync(3, attempt => TimeSpan.FromMilliseconds(50 * Math.Pow(2, attempt)), + onRetry: (exception, calculatedWaitDuration) => + { + logger?.LogWarning( + exception, + "Deletion of {TargetDirectory} failed. Retrying in {CalculatedWaitDuration}", + directory, calculatedWaitDuration); + }); + + return policy.ExecuteAsync(async () => + { + await Task.Run(() => { DeleteVerbose(directory, logger); }); + }); + } + + /// + /// Deletes a directory and all of its contents recursively. + /// Removes link targets without deleting the source. + /// + public static void DeleteVerbose(this DirectoryPath directory, ILogger? logger = default) + { + // Skip if directory does not exist + if (!directory.Exists) + { + return; + } + // For junction points, delete with recursive false + if (directory.IsSymbolicLink) + { + logger?.LogInformation("Removing junction point {TargetDirectory}", directory); + try + { + directory.Delete(false); + return; + } + catch (IOException ex) + { + throw new IOException($"Failed to delete junction point {directory}", ex); + } + } + // Recursively delete all subdirectories + foreach (var subDir in directory.Info.EnumerateDirectories()) + { + DeleteVerbose(subDir, logger); + } + + // Delete all files in the directory + foreach (var filePath in directory.Info.EnumerateFiles()) + { + try + { + filePath.Attributes = FileAttributes.Normal; + filePath.Delete(); + } + catch (IOException ex) + { + throw new IOException($"Failed to delete file {filePath.FullName}", ex); + } + } + + // Delete this directory + try + { + directory.Delete(false); + } + catch (IOException ex) + { + throw new IOException($"Failed to delete directory {directory}", ex); + } + } +} diff --git a/StabilityMatrix.Avalonia/Languages/Cultures.cs b/StabilityMatrix.Avalonia/Languages/Cultures.cs new file mode 100644 index 00000000..542fd18b --- /dev/null +++ b/StabilityMatrix.Avalonia/Languages/Cultures.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +namespace StabilityMatrix.Avalonia.Languages; + +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] +public static class Cultures +{ + public static CultureInfo Default { get; } = new("en-US"); + + public static CultureInfo Current => Resources.Culture; + + public static readonly Dictionary SupportedCulturesByCode = + new Dictionary + { + ["en-US"] = Default, + ["ja-JP"] = new("ja-JP") + }; + + public static IReadOnlyList SupportedCultures + => SupportedCulturesByCode.Values.ToImmutableList(); + + public static CultureInfo GetSupportedCultureOrDefault(string? cultureCode) + { + if (cultureCode is null + || !SupportedCulturesByCode.TryGetValue(cultureCode, out var culture)) + { + return Default; + } + + return culture; + } + + public static bool TrySetSupportedCulture(string? cultureCode) + { + if (cultureCode is null + || !SupportedCulturesByCode.TryGetValue(cultureCode, out var culture)) + { + return false; + } + + Resources.Culture = culture; + return true; + } + + public static bool TrySetSupportedCulture(CultureInfo? cultureInfo) + { + return cultureInfo is not null && TrySetSupportedCulture(cultureInfo.Name); + } +} diff --git a/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs new file mode 100644 index 00000000..27757b99 --- /dev/null +++ b/StabilityMatrix.Avalonia/Languages/Resources.Designer.cs @@ -0,0 +1,206 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace StabilityMatrix.Avalonia.Languages { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("StabilityMatrix.Avalonia.Languages.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Cancel. + /// + public static string Action_Cancel { + get { + return ResourceManager.GetString("Action_Cancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Import. + /// + public static string Action_Import { + get { + return ResourceManager.GetString("Action_Import", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Launch. + /// + public static string Action_Launch { + get { + return ResourceManager.GetString("Action_Launch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Quit. + /// + public static string Action_Quit { + get { + return ResourceManager.GetString("Action_Quit", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Relaunch. + /// + public static string Action_Relaunch { + get { + return ResourceManager.GetString("Action_Relaunch", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Relaunch Later. + /// + public static string Action_RelaunchLater { + get { + return ResourceManager.GetString("Action_RelaunchLater", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Save. + /// + public static string Action_Save { + get { + return ResourceManager.GetString("Action_Save", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Branches. + /// + public static string Label_Branches { + get { + return ResourceManager.GetString("Label_Branches", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Language. + /// + public static string Label_Language { + get { + return ResourceManager.GetString("Label_Language", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Package Type. + /// + public static string Label_PackageType { + get { + return ResourceManager.GetString("Label_PackageType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Relaunch Required. + /// + public static string Label_RelaunchRequired { + get { + return ResourceManager.GetString("Label_RelaunchRequired", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Releases. + /// + public static string Label_Releases { + get { + return ResourceManager.GetString("Label_Releases", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown Package. + /// + public static string Label_UnknownPackage { + get { + return ResourceManager.GetString("Label_UnknownPackage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Version. + /// + public static string Label_Version { + get { + return ResourceManager.GetString("Label_Version", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Version Type. + /// + public static string Label_VersionType { + get { + return ResourceManager.GetString("Label_VersionType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Relaunch is required for new language option to take effect. + /// + public static string Text_RelaunchRequiredToApplyLanguage { + get { + return ResourceManager.GetString("Text_RelaunchRequiredToApplyLanguage", resourceCulture); + } + } + } +} diff --git a/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx b/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx new file mode 100644 index 00000000..ccd583bf --- /dev/null +++ b/StabilityMatrix.Avalonia/Languages/Resources.ja-JP.resx @@ -0,0 +1,23 @@ + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 保存 + + + 戻る + + + 言語 + + diff --git a/StabilityMatrix.Avalonia/Languages/Resources.resx b/StabilityMatrix.Avalonia/Languages/Resources.resx new file mode 100644 index 00000000..93626903 --- /dev/null +++ b/StabilityMatrix.Avalonia/Languages/Resources.resx @@ -0,0 +1,69 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Launch + + + Quit + + + Save + + + Cancel + + + Language + + + Relaunch is required for new language option to take effect + + + Relaunch + + + Relaunch Later + + + Relaunch Required + + + Unknown Package + + + Import + + + Package Type + + + Version + + + Version Type + + + Releases + + + Branches + + diff --git a/StabilityMatrix.Avalonia/Services/ServiceManager.cs b/StabilityMatrix.Avalonia/Services/ServiceManager.cs index dc7387cf..ee957cec 100644 --- a/StabilityMatrix.Avalonia/Services/ServiceManager.cs +++ b/StabilityMatrix.Avalonia/Services/ServiceManager.cs @@ -1,10 +1,14 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using Avalonia.Controls; using Microsoft.Extensions.DependencyInjection; +using StabilityMatrix.Avalonia.Controls; +using StabilityMatrix.Core.Attributes; namespace StabilityMatrix.Avalonia.Services; +[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] public class ServiceManager { // Holds providers @@ -111,6 +115,48 @@ public class ServiceManager $"Service of type {typeof(TService)} is not registered for {typeof(T)}"); } + /// + /// Get a view model instance from runtime type + /// + [SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] + public T Get(Type serviceType) + { + if (!serviceType.IsAssignableFrom(typeof(T))) + { + throw new ArgumentException( + $"Service type {serviceType} is not assignable from {typeof(T)}"); + } + + if (instances.TryGetValue(serviceType, out var instance)) + { + if (instance is null) + { + throw new ArgumentException( + $"Service of type {serviceType} was registered as null"); + } + return (T) instance; + } + + if (providers.TryGetValue(serviceType, out var provider)) + { + if (provider is null) + { + throw new ArgumentException( + $"Service of type {serviceType} was registered as null"); + } + var result = provider(); + if (result is null) + { + throw new ArgumentException( + $"Service provider for type {serviceType} returned null"); + } + return (T) result; + } + + throw new ArgumentException( + $"Service of type {serviceType} is not registered for {typeof(T)}"); + } + /// /// Get a view model instance with an initializer parameter /// @@ -129,4 +175,50 @@ public class ServiceManager initializer(instance); return instance; } + + /// + /// Get a view model instance, set as DataContext of its View, and return + /// a BetterContentDialog with that View as its Content + /// + public BetterContentDialog GetDialog() where TService : T + { + var instance = Get()!; + + if (Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute)) is not ViewAttribute + viewAttr) + { + throw new InvalidOperationException($"View not found for {instance.GetType().FullName}"); + } + + if (Activator.CreateInstance(viewAttr.GetViewType()) is not Control view) + { + throw new NullReferenceException($"Unable to create instance for {instance.GetType().FullName}"); + } + + return new BetterContentDialog { Content = view }; + } + + /// + /// Get a view model instance with initializer, set as DataContext of its View, and return + /// a BetterContentDialog with that View as its Content + /// + public BetterContentDialog GetDialog(Action initializer) where TService : T + { + var instance = Get(initializer)!; + + if (Attribute.GetCustomAttribute(instance.GetType(), typeof(ViewAttribute)) is not ViewAttribute + viewAttr) + { + throw new InvalidOperationException($"View not found for {instance.GetType().FullName}"); + } + + if (Activator.CreateInstance(viewAttr.GetViewType()) is not Control view) + { + throw new NullReferenceException($"Unable to create instance for {instance.GetType().FullName}"); + } + + view.DataContext = instance; + + return new BetterContentDialog { Content = view }; + } } diff --git a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj index 2acc5271..bad8f426 100644 --- a/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj +++ b/StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj @@ -8,7 +8,7 @@ app.manifest true ./Assets/Icon.ico - 2.1.1-dev.1 + 2.3.0-dev.1 $(Version) true @@ -25,6 +25,7 @@ + @@ -76,4 +77,19 @@ + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + + + + + + True + True + Resources.resx + + diff --git a/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml b/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml index ab433d39..18de2349 100644 --- a/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml +++ b/StabilityMatrix.Avalonia/Styles/ButtonStyles.axaml @@ -11,6 +11,7 @@ + - + diff --git a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml index bb98b979..21178818 100644 --- a/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/CheckpointsPage.axaml @@ -239,17 +239,19 @@ - + - - - + + + + + - - - - - + Text="Drag & drop checkpoints here to import" + IsVisible="{Binding !CheckpointFiles.Count}"/> + IsVisible="{Binding Progress.IsTextVisible}" /> + IsVisible="{Binding Progress.IsProgressVisible}" + Value="{Binding Progress.Value, FallbackValue=20}" /> - + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml index 2332b32d..36961405 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/InstallerDialog.axaml @@ -135,7 +135,8 @@ TextWrapping="Wrap" IsVisible="{Binding SelectedPackage.Disclaimer, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"/> - + - + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml new file mode 100644 index 00000000..8cdbf093 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml.cs b/StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml.cs new file mode 100644 index 00000000..a5f8cf50 --- /dev/null +++ b/StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia.Markup.Xaml; +using StabilityMatrix.Avalonia.Controls; + +namespace StabilityMatrix.Avalonia.Views.Dialogs; + +public partial class PackageImportDialog : UserControlBase +{ + public PackageImportDialog() + { + InitializeComponent(); + } + + private void InitializeComponent() + { + AvaloniaXamlLoader.Load(this); + } +} diff --git a/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml b/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml index bdbccdc5..2fe90df8 100644 --- a/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml +++ b/StabilityMatrix.Avalonia/Views/Dialogs/SelectDataDirectoryDialog.axaml @@ -50,7 +50,7 @@ + Margin="0,32,0,0" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml b/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml index f45ab5d3..44955c68 100644 --- a/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml +++ b/StabilityMatrix.Avalonia/Views/ProgressManagerPage.axaml @@ -1,46 +1,156 @@ - + - - + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + @@ -51,23 +161,11 @@ - - - - - - - - - - + - - + + diff --git a/StabilityMatrix.Avalonia/Views/SettingsPage.axaml b/StabilityMatrix.Avalonia/Views/SettingsPage.axaml index 8e3d6d0d..cb745ba9 100644 --- a/StabilityMatrix.Avalonia/Views/SettingsPage.axaml +++ b/StabilityMatrix.Avalonia/Views/SettingsPage.axaml @@ -1,317 +1,368 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +