Browse Source

Merge pull request #149 from ionite34/model-browser-features

pull/55/head
Ionite 1 year ago committed by GitHub
parent
commit
ad9ce6797d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      StabilityMatrix/CheckpointBrowserPage.xaml
  2. 5
      StabilityMatrix/CheckpointBrowserPage.xaml.cs
  3. 52
      StabilityMatrix/DesignData/MockCheckpointBrowserViewModel.cs
  4. 34
      StabilityMatrix/Helper/Expressions.cs
  5. 57
      StabilityMatrix/Helper/ISettingsManager.cs
  6. 207
      StabilityMatrix/Helper/SettingsManager.cs
  7. 14
      StabilityMatrix/MainWindow.xaml.cs
  8. 27
      StabilityMatrix/Models/Settings/Settings.cs
  9. 35
      StabilityMatrix/Models/Settings/SettingsTransaction.cs
  10. 4
      StabilityMatrix/Services/NotificationBarService.cs
  11. 18
      StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs
  12. 41
      StabilityMatrix/ViewModels/CheckpointBrowserViewModel.cs
  13. 5
      StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs
  14. 18
      StabilityMatrix/ViewModels/DataDirectoryMigrationViewModel.cs
  15. 7
      StabilityMatrix/ViewModels/InstallerViewModel.cs
  16. 34
      StabilityMatrix/ViewModels/LaunchViewModel.cs
  17. 5
      StabilityMatrix/ViewModels/OneClickInstallViewModel.cs
  18. 6
      StabilityMatrix/ViewModels/PackageManagerViewModel.cs
  19. 15
      StabilityMatrix/ViewModels/SettingsViewModel.cs

12
StabilityMatrix/CheckpointBrowserPage.xaml

@ -1,20 +1,19 @@
<Page <Page
Foreground="{DynamicResource TextFillColorPrimaryBrush}" Foreground="{DynamicResource TextFillColorPrimaryBrush}"
Loaded="CheckpointBrowserPage_OnLoaded"
d:DataContext="{d:DesignInstance designData:MockCheckpointBrowserViewModel, d:DataContext="{d:DesignInstance designData:MockCheckpointBrowserViewModel,
IsDesignTimeCreatable=True}" IsDesignTimeCreatable=True}"
d:DesignHeight="600" d:DesignHeight="650"
d:DesignWidth="650" d:DesignWidth="750"
mc:Ignorable="d" mc:Ignorable="d"
ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}" ui:Design.Background="{DynamicResource ApplicationBackgroundBrush}"
ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}" ui:Design.Foreground="{DynamicResource TextFillColorPrimaryBrush}"
x:Class="StabilityMatrix.CheckpointBrowserPage" x:Class="StabilityMatrix.CheckpointBrowserPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:api="clr-namespace:StabilityMatrix.Models.Api"
xmlns:converters="clr-namespace:StabilityMatrix.Converters" xmlns:converters="clr-namespace:StabilityMatrix.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:designData="clr-namespace:StabilityMatrix.DesignData" xmlns:designData="clr-namespace:StabilityMatrix.DesignData"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="clr-namespace:StabilityMatrix.Models"
xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml"
xmlns:viewModels="clr-namespace:StabilityMatrix.ViewModels" xmlns:viewModels="clr-namespace:StabilityMatrix.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@ -24,8 +23,7 @@
<converters:BoolNegationConverter /> <converters:BoolNegationConverter />
<BooleanToVisibilityConverter /> <BooleanToVisibilityConverter />
</converters:ValueConverterGroup> </converters:ValueConverterGroup>
<converters:UriToBitmapConverter x:Key="UriToBitmapConverter" />
<BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" /> <BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<DataTemplate DataType="{x:Type viewModels:CheckpointBrowserCardViewModel}" x:Key="CivitModelTemplate"> <DataTemplate DataType="{x:Type viewModels:CheckpointBrowserCardViewModel}" x:Key="CivitModelTemplate">
@ -45,7 +43,7 @@
FontSize="11" FontSize="11"
Foreground="{DynamicResource TextFillColorTertiaryBrush}" Foreground="{DynamicResource TextFillColorTertiaryBrush}"
Margin="0,2,0,0" Margin="0,2,0,0"
Text="{Binding CivitModel.ModelVersions[0].Name}" Text="{Binding CivitModel.ModelVersions[0].Name, FallbackValue=''}"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
<Grid> <Grid>
<Image <Image

5
StabilityMatrix/CheckpointBrowserPage.xaml.cs

@ -33,4 +33,9 @@ public partial class CheckpointBrowserPage : Page
{ {
e.Handled = true; e.Handled = true;
} }
private void CheckpointBrowserPage_OnLoaded(object sender, RoutedEventArgs e)
{
(DataContext as CheckpointBrowserViewModel)?.OnLoaded();
}
} }

52
StabilityMatrix/DesignData/MockCheckpointBrowserViewModel.cs

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Media.Imaging; using System.Windows.Media.Imaging;
using StabilityMatrix.Models.Api; using StabilityMatrix.Models.Api;
using StabilityMatrix.ViewModels; using StabilityMatrix.ViewModels;
@ -14,30 +16,56 @@ public class MockCheckpointBrowserViewModel : CheckpointBrowserViewModel
{ {
ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel> ModelCards = new ObservableCollection<CheckpointBrowserCardViewModel>
{ {
new(null!, null!, null!, null!) new(null!, null!, null!, null!,
fixedImage: new BitmapImage(new Uri("https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/01bb1e4b-f88b-441a-b2bf-c1ec0bd91727/width=450/00015-1007989405.jpeg")))
{ {
CivitModel = new() CivitModel = new CivitModel
{ {
Name = "bb95 Furry Mix", Name = "Ghibli Background",
ModelVersions = new() Type = CivitModelType.Checkpoint,
ModelVersions = new List<CivitModelVersion>
{ {
new CivitModelVersion new()
{ {
Name = "v7.0", Name = "v5.0",
Images = new() Files = new List<CivitFile>
{ {
new CivitImage new()
{ {
Url = Name = "File",
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/1547f350-461a-4cd0-a753-0544aa81e4fc/width=450/00000-4137473915.jpeg" SizeKb = 844110,
}
}
}
}
},
},
new(null!, null!, null!, null!,
fixedImage: new BitmapImage(new Uri("https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/fcb56b61-2cd6-40f4-a75d-e9448c0822d6/width=1152/29933-2136606019-masterpiece.jpeg")))
{
CivitModel = new CivitModel
{
Name = "Indigo Furry mix",
Type = CivitModelType.Checkpoint,
Nsfw = false,
ModelVersions = new List<CivitModelVersion>
{
new()
{
Name = "v45_hybrid",
Files = new List<CivitFile>
{
new()
{
Name = "Pruned Model fp16",
SizeKb = 1990000,
} }
} }
} }
} }
}, },
CardImage = new BitmapImage(new Uri(
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/1547f350-461a-4cd0-a753-0544aa81e4fc/width=450/00000-4137473915.jpeg"))
} }
}; };
ModelCardsView = CollectionViewSource.GetDefaultView(ModelCards);
} }
} }

34
StabilityMatrix/Helper/Expressions.cs

@ -0,0 +1,34 @@
using System;
using System.Linq.Expressions;
using System.Reflection;
namespace StabilityMatrix.Helper;
public static class Expressions
{
public static (string propertyName, Expression<Action<T, TValue>> assigner)
GetAssigner<T, TValue>(Expression<Func<T, TValue>> propertyAccessor)
{
if (propertyAccessor.Body is not MemberExpression memberExpression)
{
throw new ArgumentException(
$"Expression must be a member expression, not {propertyAccessor.Body.NodeType}");
}
var propertyInfo = memberExpression.Member as PropertyInfo;
if (propertyInfo == null)
{
throw new ArgumentException(
$"Expression member must be a property, not {memberExpression.Member.MemberType}");
}
var propertyName = propertyInfo.Name;
var typeParam = Expression.Parameter(typeof(T));
var valueParam = Expression.Parameter(typeof(TValue));
var expr = Expression.Lambda<Action<T, TValue>>(
Expression.Assign(
Expression.MakeMemberAccess(typeParam, propertyInfo),
valueParam), typeParam, valueParam);
return (propertyName, expr);
}
}

57
StabilityMatrix/Helper/ISettingsManager.cs

@ -1,5 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using StabilityMatrix.Models; using StabilityMatrix.Models;
using StabilityMatrix.Models.Settings; using StabilityMatrix.Models.Settings;
using Wpf.Ui.Controls.Window; using Wpf.Ui.Controls.Window;
@ -11,28 +13,56 @@ public interface ISettingsManager
Settings Settings { get; } Settings Settings { get; }
// Events // Events
event EventHandler<bool>? ModelBrowserNsfwEnabledChanged;
event EventHandler<string>? LibraryDirChanged; event EventHandler<string>? LibraryDirChanged;
// Library settings // Library settings
bool IsPortableMode { get; } bool IsPortableMode { get; }
string LibraryDir { get; } string LibraryDir { get; }
bool IsLibraryDirSet { get; }
bool TryFindLibrary(); bool TryFindLibrary();
// Dynamic paths from library // Dynamic paths from library
string DatabasePath { get; } string DatabasePath { get; }
string ModelsDirectory { get; } string ModelsDirectory { get; }
/// <summary>
/// Return a SettingsTransaction that can be used to modify Settings
/// Saves on Dispose.
/// </summary>
public SettingsTransaction BeginTransaction();
/// <summary>
/// Execute a function that modifies Settings
/// Commits changes after the function returns.
/// </summary>
/// <param name="func">Function accepting Settings to modify</param>
public void Transaction(Action<Settings> func);
/// <summary>
/// Modify a settings property by expression and commit changes.
/// This will notify listeners of SettingsPropertyChanged.
/// </summary>
public void Transaction<TValue>(Expression<Func<Settings, TValue>> expression, TValue value);
/// <summary>
/// Register a source observable object and property to be relayed to Settings
/// </summary>
public void RelayPropertyFor<T, TValue>(
T source,
Expression<Func<T, TValue>> sourceProperty,
Expression<Func<Settings, TValue>> settingsProperty) where T : INotifyPropertyChanged;
/// <summary>
/// Register an Action to be called on change of the settings property.
/// </summary>
public void RegisterPropertyChangedHandler<T>(
Expression<Func<Settings, T>> settingsProperty,
Action<T> onPropertyChanged);
// Migration // Migration
IEnumerable<InstalledPackage> GetOldInstalledPackages(); IEnumerable<InstalledPackage> GetOldInstalledPackages();
void SetTheme(string theme);
void AddInstalledPackage(InstalledPackage p);
void RemoveInstalledPackage(InstalledPackage p);
void SetActiveInstalledPackage(InstalledPackage? p);
void SetNavExpanded(bool navExpanded);
void AddPathExtension(string pathExtension); void AddPathExtension(string pathExtension);
string GetPathExtensionsAsString();
/// <summary> /// <summary>
/// Insert path extensions to the front of the PATH environment variable /// Insert path extensions to the front of the PATH environment variable
@ -43,14 +73,8 @@ public interface ISettingsManager
void SetLastUpdateCheck(InstalledPackage package); void SetLastUpdateCheck(InstalledPackage package);
List<LaunchOption> GetLaunchArgs(Guid packageId); List<LaunchOption> GetLaunchArgs(Guid packageId);
void SaveLaunchArgs(Guid packageId, List<LaunchOption> launchArgs); void SaveLaunchArgs(Guid packageId, List<LaunchOption> launchArgs);
void SetWindowBackdropType(WindowBackdropType backdropType);
void SetHasSeenWelcomeNotification(bool hasSeenWelcomeNotification);
string? GetActivePackageHost(); string? GetActivePackageHost();
string? GetActivePackagePort(); string? GetActivePackagePort();
void SetWebApiHost(string? host);
void SetWebApiPort(string? port);
void SetFirstLaunchSetupComplete(bool firstLaunchSetupCompleted);
void SetModelBrowserNsfwEnabled(bool value);
void SetSharedFolderCategoryVisible(SharedFolderType type, bool visible); void SetSharedFolderCategoryVisible(SharedFolderType type, bool visible);
bool IsSharedFolderCategoryVisible(SharedFolderType type); bool IsSharedFolderCategoryVisible(SharedFolderType type);
bool IsEulaAccepted(); bool IsEulaAccepted();
@ -66,10 +90,5 @@ public interface ISettingsManager
/// Creates the ./Data directory and the `.sm-portable` marker file /// Creates the ./Data directory and the `.sm-portable` marker file
/// </summary> /// </summary>
void SetPortableMode(); void SetPortableMode();
void SetPlacement(string placementStr);
void SetSearchOptions(ModelSearchOptions options);
Guid GetOldActivePackageId(); Guid GetOldActivePackageId();
void SetActiveInstalledPackage(Guid guid);
void SetIsImportAsConnected(bool value);
void SetKeepFolderLinksOnShutdown(bool value);
} }

207
StabilityMatrix/Helper/SettingsManager.cs

@ -1,10 +1,15 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using NLog; using NLog;
using Refit; using Refit;
using StabilityMatrix.Models; using StabilityMatrix.Models;
@ -44,6 +49,7 @@ public class SettingsManager : ISettingsManager
LibraryDirChanged?.Invoke(this, value); LibraryDirChanged?.Invoke(this, value);
} }
} }
public bool IsLibraryDirSet => !string.IsNullOrWhiteSpace(libraryDir);
// Dynamic paths from library // Dynamic paths from library
public string DatabasePath => Path.Combine(LibraryDir, "StabilityMatrix.db"); public string DatabasePath => Path.Combine(LibraryDir, "StabilityMatrix.db");
@ -51,11 +57,102 @@ public class SettingsManager : ISettingsManager
public string ModelsDirectory => Path.Combine(LibraryDir, "Models"); public string ModelsDirectory => Path.Combine(LibraryDir, "Models");
public Settings Settings { get; private set; } = new(); public Settings Settings { get; private set; } = new();
// Events
public event EventHandler<bool>? ModelBrowserNsfwEnabledChanged;
public event EventHandler<string>? LibraryDirChanged; public event EventHandler<string>? LibraryDirChanged;
public event EventHandler<PropertyChangedEventArgs>? SettingsPropertyChanged;
/// <inheritdoc />
public SettingsTransaction BeginTransaction()
{
return new SettingsTransaction(this, SaveSettingsAsync);
}
/// <inheritdoc />
public void Transaction(Action<Settings> func)
{
using var transaction = BeginTransaction();
func(transaction.Settings);
transaction.Dispose();
}
/// <inheritdoc />
public void Transaction<TValue>(Expression<Func<Settings, TValue>> expression, TValue value)
{
if (expression.Body is not MemberExpression memberExpression)
{
throw new ArgumentException(
$"Expression must be a member expression, not {expression.Body.NodeType}");
}
var propertyInfo = memberExpression.Member as PropertyInfo;
if (propertyInfo == null)
{
throw new ArgumentException(
$"Expression member must be a property, not {memberExpression.Member.MemberType}");
}
var name = propertyInfo.Name;
// Set value
using var transaction = BeginTransaction();
propertyInfo.SetValue(transaction.Settings, value);
// Invoke property changed event
SettingsPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
/// <inheritdoc />
public void RelayPropertyFor<T, TValue>(
T source,
Expression<Func<T, TValue>> sourceProperty,
Expression<Func<Settings, TValue>> settingsProperty) where T : INotifyPropertyChanged
{
var sourceGetter = sourceProperty.Compile();
var (propertyName, assigner) = Expressions.GetAssigner(sourceProperty);
var sourceSetter = assigner.Compile();
var settingsGetter = settingsProperty.Compile();
var (_, settingsAssigner) = Expressions.GetAssigner(settingsProperty);
var settingsSetter = settingsAssigner.Compile();
// Update source when settings change
SettingsPropertyChanged += (_, args) =>
{
if (args.PropertyName != propertyName) return;
sourceSetter(source, settingsGetter(Settings));
};
// Set and Save settings when source changes
source.PropertyChanged += (_, args) =>
{
if (args.PropertyName != propertyName) return;
settingsSetter(Settings, sourceGetter(source));
SaveSettingsAsync().SafeFireAndForget();
// Invoke property changed event
SettingsPropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
};
}
/// <inheritdoc />
public void RegisterPropertyChangedHandler<T>(
Expression<Func<Settings, T>> settingsProperty,
Action<T> onPropertyChanged)
{
var settingsGetter = settingsProperty.Compile();
var (propertyName, _) = Expressions.GetAssigner(settingsProperty);
// Invoke handler when settings change
SettingsPropertyChanged += (_, args) =>
{
if (args.PropertyName != propertyName) return;
onPropertyChanged(settingsGetter(Settings));
};
}
/// <summary> /// <summary>
/// Attempts to locate and set the library path /// Attempts to locate and set the library path
/// Return true if found, false otherwise /// Return true if found, false otherwise
@ -190,54 +287,6 @@ public class SettingsManager : ISettingsManager
return oldSettings.ActiveInstalledPackage ?? default; return oldSettings.ActiveInstalledPackage ?? default;
} }
public void SetTheme(string theme)
{
Settings.Theme = theme;
SaveSettings();
}
public void AddInstalledPackage(InstalledPackage p)
{
Settings.InstalledPackages.Add(p);
SaveSettings();
}
public void RemoveInstalledPackage(InstalledPackage p)
{
Settings.InstalledPackages.Remove(p);
SetActiveInstalledPackage(Settings.InstalledPackages.Any() ? Settings.InstalledPackages.First() : null);
}
public void SetActiveInstalledPackage(InstalledPackage? p)
{
Settings.ActiveInstalledPackage = p?.Id;
SaveSettings();
}
public void SetActiveInstalledPackage(Guid guid)
{
Settings.ActiveInstalledPackage = guid;
SaveSettings();
}
public void SetNavExpanded(bool navExpanded)
{
Settings.IsNavExpanded = navExpanded;
SaveSettings();
}
public void SetIsImportAsConnected(bool value)
{
Settings.IsImportAsConnected = value;
SaveSettings();
}
public void SetKeepFolderLinksOnShutdown(bool value)
{
Settings.KeepFolderLinksOnShutdown = value;
SaveSettings();
}
public void AddPathExtension(string pathExtension) public void AddPathExtension(string pathExtension)
{ {
@ -250,7 +299,7 @@ public class SettingsManager : ISettingsManager
{ {
return string.Join(";", Settings.PathExtensions ?? new List<string>()); return string.Join(";", Settings.PathExtensions ?? new List<string>());
} }
/// <summary> /// <summary>
/// Insert path extensions to the front of the PATH environment variable /// Insert path extensions to the front of the PATH environment variable
/// </summary> /// </summary>
@ -308,18 +357,6 @@ public class SettingsManager : ISettingsManager
packageData.LaunchArgs = toSave; packageData.LaunchArgs = toSave;
SaveSettings(); SaveSettings();
} }
public void SetWindowBackdropType(WindowBackdropType backdropType)
{
Settings.WindowBackdropType = backdropType;
SaveSettings();
}
public void SetHasSeenWelcomeNotification(bool hasSeenWelcomeNotification)
{
Settings.HasSeenWelcomeNotification = hasSeenWelcomeNotification;
SaveSettings();
}
public string? GetActivePackageHost() public string? GetActivePackageHost()
{ {
@ -344,31 +381,6 @@ public class SettingsManager : ISettingsManager
} }
return portOption?.DefaultValue as string; return portOption?.DefaultValue as string;
} }
public void SetWebApiHost(string? host)
{
Settings.WebApiHost = host;
SaveSettings();
}
public void SetWebApiPort(string? port)
{
Settings.WebApiPort = port;
SaveSettings();
}
public void SetFirstLaunchSetupComplete(bool value)
{
Settings.FirstLaunchSetupComplete = value;
SaveSettings();
}
public void SetModelBrowserNsfwEnabled(bool value)
{
Settings.ModelBrowserNsfwEnabled = value;
ModelBrowserNsfwEnabledChanged?.Invoke(this, value);
SaveSettings();
}
public void SetSharedFolderCategoryVisible(SharedFolderType type, bool visible) public void SetSharedFolderCategoryVisible(SharedFolderType type, bool visible)
{ {
@ -413,18 +425,6 @@ public class SettingsManager : ISettingsManager
File.WriteAllText(GlobalSettingsPath, json); File.WriteAllText(GlobalSettingsPath, json);
} }
public void SetPlacement(string placementStr)
{
Settings.Placement = placementStr;
SaveSettings();
}
public void SetSearchOptions(ModelSearchOptions options)
{
Settings.ModelSearchOptions = options;
SaveSettings();
}
/// <summary> /// <summary>
/// Loads settings from the settings file /// Loads settings from the settings file
/// If the settings file does not exist, it will be created with default values /// If the settings file does not exist, it will be created with default values
@ -480,4 +480,9 @@ public class SettingsManager : ISettingsManager
FileLock.ExitWriteLock(); FileLock.ExitWriteLock();
} }
} }
private Task SaveSettingsAsync()
{
return Task.Run(SaveSettings);
}
} }

14
StabilityMatrix/MainWindow.xaml.cs

@ -61,17 +61,17 @@ namespace StabilityMatrix
private void RootNavigation_OnPaneOpened(NavigationView sender, RoutedEventArgs args) private void RootNavigation_OnPaneOpened(NavigationView sender, RoutedEventArgs args)
{ {
if (settingsManager.TryFindLibrary()) if (settingsManager.IsLibraryDirSet)
{ {
settingsManager.SetNavExpanded(true); settingsManager.Transaction(s => s.IsNavExpanded = true);
} }
} }
private void RootNavigation_OnPaneClosed(NavigationView sender, RoutedEventArgs args) private void RootNavigation_OnPaneClosed(NavigationView sender, RoutedEventArgs args)
{ {
if (settingsManager.TryFindLibrary()) if (settingsManager.IsLibraryDirSet)
{ {
settingsManager.SetNavExpanded(false); settingsManager.Transaction(s => s.IsNavExpanded = false);
} }
} }
@ -97,7 +97,7 @@ namespace StabilityMatrix
var interopHelper = new WindowInteropHelper(this); var interopHelper = new WindowInteropHelper(this);
var placement = ScreenExtensions.GetPlacement(interopHelper.Handle); var placement = ScreenExtensions.GetPlacement(interopHelper.Handle);
settingsManager.SetPlacement(placement.ToString()); settingsManager.Transaction(s => s.Placement = placement.ToString());
}); });
} }
@ -106,9 +106,9 @@ namespace StabilityMatrix
var interopHelper = new WindowInteropHelper(this); var interopHelper = new WindowInteropHelper(this);
var placement = ScreenExtensions.GetPlacement(interopHelper.Handle); var placement = ScreenExtensions.GetPlacement(interopHelper.Handle);
if (settingsManager.TryFindLibrary()) if (settingsManager.IsLibraryDirSet)
{ {
settingsManager.SetPlacement(placement.ToString()); settingsManager.Transaction(s => s.Placement = placement.ToString());
} }
} }
} }

27
StabilityMatrix/Models/Settings/Settings.cs

@ -39,4 +39,31 @@ public class Settings
{ {
return InstalledPackages.FirstOrDefault(x => x.Id == ActiveInstalledPackage); return InstalledPackages.FirstOrDefault(x => x.Id == ActiveInstalledPackage);
} }
public void RemoveInstalledPackageAndUpdateActive(InstalledPackage package)
{
RemoveInstalledPackageAndUpdateActive(package.Id);
}
public void RemoveInstalledPackageAndUpdateActive(Guid id)
{
InstalledPackages.RemoveAll(x => x.Id == id);
UpdateActiveInstalledPackage();
}
// Update ActiveInstalledPackage if not valid
// uses first package or null if no packages
private void UpdateActiveInstalledPackage()
{
// Empty packages - set to null
if (InstalledPackages.Count == 0)
{
ActiveInstalledPackage = null;
}
// Active package is not in package - set to first package
else if (InstalledPackages.All(x => x.Id != ActiveInstalledPackage))
{
ActiveInstalledPackage = InstalledPackages[0].Id;
}
}
} }

35
StabilityMatrix/Models/Settings/SettingsTransaction.cs

@ -0,0 +1,35 @@
using System;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using StabilityMatrix.Helper;
namespace StabilityMatrix.Models.Settings;
/// <summary>
/// Transaction object which saves settings manager changes when disposed.
/// </summary>
public class SettingsTransaction : IDisposable, IAsyncDisposable
{
private readonly ISettingsManager settingsManager;
private readonly Func<Task> onCommit;
public Settings Settings => settingsManager.Settings;
public SettingsTransaction(ISettingsManager settingsManager, Func<Task> onCommit)
{
this.settingsManager = settingsManager;
this.onCommit = onCommit;
}
public void Dispose()
{
onCommit().SafeFireAndForget();
GC.SuppressFinalize(this);
}
public async ValueTask DisposeAsync()
{
await onCommit();
GC.SuppressFinalize(this);
}
}

4
StabilityMatrix/Services/NotificationBarService.cs

@ -27,7 +27,7 @@ public class NotificationBarService : SnackbarService, INotificationBarService
"Welcome to StabilityMatrix!", "Welcome to StabilityMatrix!",
"You can join our Discord server for support and feedback.", linkIcon, ControlAppearance.Info); "You can join our Discord server for support and feedback.", linkIcon, ControlAppearance.Info);
snackbar.SafeFireAndForget(); snackbar.SafeFireAndForget();
settingsManager.SetHasSeenWelcomeNotification(true); settingsManager.Transaction(s => s.HasSeenWelcomeNotification = true);
} }
} }

18
StabilityMatrix/ViewModels/CheckpointBrowserCardViewModel.cs

@ -33,15 +33,29 @@ public partial class CheckpointBrowserCardViewModel : ProgressViewModel
public override Visibility ProgressVisibility => Value > 0 ? Visibility.Visible : Visibility.Collapsed; public override Visibility ProgressVisibility => Value > 0 ? Visibility.Visible : Visibility.Collapsed;
public override Visibility TextVisibility => Value > 0 ? Visibility.Visible : Visibility.Collapsed; public override Visibility TextVisibility => Value > 0 ? Visibility.Visible : Visibility.Collapsed;
public CheckpointBrowserCardViewModel(CivitModel civitModel, IDownloadService downloadService, ISnackbarService snackbarService, ISettingsManager settingsManager) public CheckpointBrowserCardViewModel(
CivitModel civitModel,
IDownloadService downloadService,
ISnackbarService snackbarService,
ISettingsManager settingsManager,
BitmapImage? fixedImage = null)
{ {
this.downloadService = downloadService; this.downloadService = downloadService;
this.snackbarService = snackbarService; this.snackbarService = snackbarService;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
CivitModel = civitModel; CivitModel = civitModel;
if (fixedImage != null)
{
CardImage = fixedImage;
return;
}
CardImage = GetImage(); CardImage = GetImage();
this.settingsManager.ModelBrowserNsfwEnabledChanged += OnNsfwModeChanged; // Update image when nsfw setting changes
settingsManager.RegisterPropertyChangedHandler(
s => s.ModelBrowserNsfwEnabled,
_ => CardImage = GetImage());
} }
// Choose and load image based on nsfw setting // Choose and load image based on nsfw setting

41
StabilityMatrix/ViewModels/CheckpointBrowserViewModel.cs

@ -35,12 +35,12 @@ public partial class CheckpointBrowserViewModel : ObservableObject
[ObservableProperty] private ObservableCollection<CheckpointBrowserCardViewModel>? modelCards; [ObservableProperty] private ObservableCollection<CheckpointBrowserCardViewModel>? modelCards;
[ObservableProperty] private ICollectionView? modelCardsView; [ObservableProperty] private ICollectionView? modelCardsView;
[ObservableProperty] private string? searchQuery; [ObservableProperty] private string searchQuery = string.Empty;
[ObservableProperty] private bool showNsfw; [ObservableProperty] private bool showNsfw;
[ObservableProperty] private bool showMainLoadingSpinner; [ObservableProperty] private bool showMainLoadingSpinner;
[ObservableProperty] private CivitPeriod selectedPeriod; [ObservableProperty] private CivitPeriod selectedPeriod = CivitPeriod.Month;
[ObservableProperty] private CivitSortMode sortMode; [ObservableProperty] private CivitSortMode sortMode = CivitSortMode.HighestRated;
[ObservableProperty] private CivitModelType selectedModelType; [ObservableProperty] private CivitModelType selectedModelType = CivitModelType.Checkpoint;
[ObservableProperty] private int currentPageNumber; [ObservableProperty] private int currentPageNumber;
[ObservableProperty] private int totalPages; [ObservableProperty] private int totalPages;
[ObservableProperty] private bool hasSearched; [ObservableProperty] private bool hasSearched;
@ -70,17 +70,23 @@ public partial class CheckpointBrowserViewModel : ObservableObject
this.snackbarService = snackbarService; this.snackbarService = snackbarService;
this.settingsManager = settingsManager; this.settingsManager = settingsManager;
this.liteDbContext = liteDbContext; this.liteDbContext = liteDbContext;
CurrentPageNumber = 1;
CanGoToNextPage = true;
}
public void OnLoaded()
{
var searchOptions = settingsManager.Settings.ModelSearchOptions; var searchOptions = settingsManager.Settings.ModelSearchOptions;
ShowNsfw = settingsManager.Settings.ModelBrowserNsfwEnabled;
SelectedPeriod = searchOptions?.SelectedPeriod ?? CivitPeriod.Month; SelectedPeriod = searchOptions?.SelectedPeriod ?? CivitPeriod.Month;
SortMode = searchOptions?.SortMode ?? CivitSortMode.HighestRated; SortMode = searchOptions?.SortMode ?? CivitSortMode.HighestRated;
SelectedModelType = searchOptions?.SelectedModelType ?? CivitModelType.Checkpoint; SelectedModelType = searchOptions?.SelectedModelType ?? CivitModelType.Checkpoint;
HasSearched = false;
CurrentPageNumber = 1; ShowNsfw = settingsManager.Settings.ModelBrowserNsfwEnabled;
CanGoToPreviousPage = false;
CanGoToNextPage = true; settingsManager.RelayPropertyFor(this, model => model.ShowNsfw,
settings => settings.ModelBrowserNsfwEnabled);
} }
/// <summary> /// <summary>
@ -209,7 +215,6 @@ public partial class CheckpointBrowserViewModel : ObservableObject
[RelayCommand] [RelayCommand]
private async Task SearchModels() private async Task SearchModels()
{ {
if (string.IsNullOrWhiteSpace(SearchQuery)) return;
var timer = Stopwatch.StartNew(); var timer = Stopwatch.StartNew();
if (SearchQuery != previousSearchQuery) if (SearchQuery != previousSearchQuery)
@ -314,7 +319,7 @@ public partial class CheckpointBrowserViewModel : ObservableObject
partial void OnShowNsfwChanged(bool value) partial void OnShowNsfwChanged(bool value)
{ {
settingsManager.SetModelBrowserNsfwEnabled(value); settingsManager.Transaction(s => s.ModelBrowserNsfwEnabled = value);
ModelCardsView?.Refresh(); ModelCardsView?.Refresh();
if (!HasSearched) if (!HasSearched)
@ -326,22 +331,22 @@ public partial class CheckpointBrowserViewModel : ObservableObject
partial void OnSelectedPeriodChanged(CivitPeriod oldValue, CivitPeriod newValue) partial void OnSelectedPeriodChanged(CivitPeriod oldValue, CivitPeriod newValue)
{ {
TrySearchAgain().SafeFireAndForget(); TrySearchAgain().SafeFireAndForget();
settingsManager.SetSearchOptions(new ModelSearchOptions(newValue, SortMode, settingsManager.Transaction(s => s.ModelSearchOptions = new ModelSearchOptions(
SelectedModelType)); newValue, SortMode, SelectedModelType));
} }
partial void OnSortModeChanged(CivitSortMode oldValue, CivitSortMode newValue) partial void OnSortModeChanged(CivitSortMode oldValue, CivitSortMode newValue)
{ {
TrySearchAgain().SafeFireAndForget(); TrySearchAgain().SafeFireAndForget();
settingsManager.SetSearchOptions(new ModelSearchOptions(SelectedPeriod, newValue, settingsManager.Transaction(s => s.ModelSearchOptions = new ModelSearchOptions(
SelectedModelType)); SelectedPeriod, newValue, SelectedModelType));
} }
partial void OnSelectedModelTypeChanged(CivitModelType oldValue, CivitModelType newValue) partial void OnSelectedModelTypeChanged(CivitModelType oldValue, CivitModelType newValue)
{ {
TrySearchAgain().SafeFireAndForget(); TrySearchAgain().SafeFireAndForget();
settingsManager.SetSearchOptions(new ModelSearchOptions(SelectedPeriod, SortMode, settingsManager.Transaction(s => s.ModelSearchOptions = new ModelSearchOptions(
newValue)); SelectedPeriod, SortMode, newValue));
} }
private async Task TrySearchAgain(bool shouldUpdatePageNumber = true) private async Task TrySearchAgain(bool shouldUpdatePageNumber = true)

5
StabilityMatrix/ViewModels/CheckpointManagerViewModel.cs

@ -25,9 +25,10 @@ public partial class CheckpointManagerViewModel : ObservableObject
partial void OnIsImportAsConnectedChanged(bool value) partial void OnIsImportAsConnectedChanged(bool value)
{ {
if (value != settingsManager.Settings.IsImportAsConnected) if (settingsManager.IsLibraryDirSet &&
value != settingsManager.Settings.IsImportAsConnected)
{ {
settingsManager.SetIsImportAsConnected(value); settingsManager.Transaction(s => s.IsImportAsConnected = value);
} }
} }

18
StabilityMatrix/ViewModels/DataDirectoryMigrationViewModel.cs

@ -155,9 +155,12 @@ public partial class DataDirectoryMigrationViewModel : ObservableObject
var oldPackages = settingsManager.GetOldInstalledPackages().ToArray(); var oldPackages = settingsManager.GetOldInstalledPackages().ToArray();
MigrateProgressText = "Migrating Packages..."; MigrateProgressText = "Migrating Packages...";
await using var st = settingsManager.BeginTransaction();
foreach (var package in oldPackages) foreach (var package in oldPackages)
{ {
settingsManager.RemoveInstalledPackage(package); st.Settings.RemoveInstalledPackageAndUpdateActive(package);
#pragma warning disable CS0618 #pragma warning disable CS0618
Logger.Info($"Migrating package {MigrateProgressCount} of {oldPackages.Length} at path {package.Path}"); Logger.Info($"Migrating package {MigrateProgressCount} of {oldPackages.Length} at path {package.Path}");
@ -165,16 +168,19 @@ public partial class DataDirectoryMigrationViewModel : ObservableObject
await package.MigratePath(); await package.MigratePath();
MigrateProgressCount++; MigrateProgressCount++;
settingsManager.AddInstalledPackage(package); st.Settings.InstalledPackages.Add(package);
settingsManager.SetActiveInstalledPackage(package); st.Settings.ActiveInstalledPackage = package.Id;
if (oldLibraryPath != libraryPath) if (oldLibraryPath != libraryPath)
{ {
// setup model links again // setup model links again
var basePackage = packageFactory.FindPackageByName(package.PackageName); if (!string.IsNullOrWhiteSpace(package.PackageName))
if (basePackage != default)
{ {
sharedFolders.SetupLinksForPackage(basePackage, package.FullPath!); var basePackage = packageFactory.FindPackageByName(package.PackageName);
if (basePackage != default)
{
sharedFolders.SetupLinksForPackage(basePackage, package.FullPath!);
}
} }
} }

7
StabilityMatrix/ViewModels/InstallerViewModel.cs

@ -339,9 +339,10 @@ public partial class InstallerViewModel : ObservableObject
LaunchCommand = SelectedPackage.LaunchCommand, LaunchCommand = SelectedPackage.LaunchCommand,
LastUpdateCheck = DateTimeOffset.Now LastUpdateCheck = DateTimeOffset.Now
}; };
settingsManager.AddInstalledPackage(package); await using var st = settingsManager.BeginTransaction();
settingsManager.SetActiveInstalledPackage(package); st.Settings.InstalledPackages.Add(package);
st.Settings.ActiveInstalledPackage = package.Id;
ProgressValue = 0; ProgressValue = 0;
} }

34
StabilityMatrix/ViewModels/LaunchViewModel.cs

@ -33,7 +33,8 @@ public partial class LaunchViewModel : ObservableObject
private readonly ISharedFolders sharedFolders; private readonly ISharedFolders sharedFolders;
private BasePackage? runningPackage; private BasePackage? runningPackage;
private bool clearingPackages = false; private bool clearingPackages;
private string webUiUrl = string.Empty;
[ObservableProperty] private string consoleInput = ""; [ObservableProperty] private string consoleInput = "";
[ObservableProperty] private Visibility launchButtonVisibility; [ObservableProperty] private Visibility launchButtonVisibility;
@ -41,29 +42,10 @@ public partial class LaunchViewModel : ObservableObject
[ObservableProperty] private bool isLaunchTeachingTipsOpen = false; [ObservableProperty] private bool isLaunchTeachingTipsOpen = false;
[ObservableProperty] private bool showWebUiButton; [ObservableProperty] private bool showWebUiButton;
[ObservableProperty] private ObservableCollection<string>? consoleHistory; [ObservableProperty] private ObservableCollection<string>? consoleHistory;
private string webUiUrl = string.Empty; [ObservableProperty] private InstalledPackage? selectedPackage;
private InstalledPackage? selectedPackage;
public InstalledPackage? SelectedPackage
{
get => selectedPackage;
set
{
if (value == selectedPackage) return;
selectedPackage = value;
if (!clearingPackages)
{
settingsManager.SetActiveInstalledPackage(value);
}
OnPropertyChanged();
}
}
[ObservableProperty] private ObservableCollection<InstalledPackage> installedPackages = new(); [ObservableProperty] private ObservableCollection<InstalledPackage> installedPackages = new();
public LaunchViewModel(ISettingsManager settingsManager, public LaunchViewModel(ISettingsManager settingsManager,
IPackageFactory packageFactory, IPackageFactory packageFactory,
IContentDialogService contentDialogService, IContentDialogService contentDialogService,
@ -90,6 +72,12 @@ public partial class LaunchViewModel : ObservableObject
ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompatOnOnActivated; ToastNotificationManagerCompat.OnActivated += ToastNotificationManagerCompatOnOnActivated;
} }
partial void OnSelectedPackageChanged(InstalledPackage? value)
{
if (clearingPackages) return;
settingsManager.Transaction(s => s.ActiveInstalledPackage = value?.Id);
}
private void OnTeachingTooltipNeeded(object? sender, EventArgs e) private void OnTeachingTooltipNeeded(object? sender, EventArgs e)
{ {

5
StabilityMatrix/ViewModels/OneClickInstallViewModel.cs

@ -137,8 +137,9 @@ public partial class OneClickInstallViewModel : ObservableObject
LaunchCommand = a1111.LaunchCommand, LaunchCommand = a1111.LaunchCommand,
LastUpdateCheck = DateTimeOffset.Now LastUpdateCheck = DateTimeOffset.Now
}; };
settingsManager.AddInstalledPackage(package); await using var st = settingsManager.BeginTransaction();
settingsManager.SetActiveInstalledPackage(package); st.Settings.InstalledPackages.Add(package);
st.Settings.ActiveInstalledPackage = package.Id;
EventManager.Instance.OnInstalledPackagesChanged(); EventManager.Instance.OnInstalledPackagesChanged();
HeaderText = "Installation complete!"; HeaderText = "Installation complete!";

6
StabilityMatrix/ViewModels/PackageManagerViewModel.cs

@ -185,7 +185,11 @@ public partial class PackageManagerViewModel : ObservableObject
{ {
snackbarService.ShowSnackbarAsync($"Package {SelectedPackage.DisplayName} uninstalled", "Success", snackbarService.ShowSnackbarAsync($"Package {SelectedPackage.DisplayName} uninstalled", "Success",
ControlAppearance.Success).SafeFireAndForget(); ControlAppearance.Success).SafeFireAndForget();
settingsManager.RemoveInstalledPackage(SelectedPackage);
settingsManager.Transaction(settings =>
{
settings.RemoveInstalledPackageAndUpdateActive(SelectedPackage);
});
} }
await OnLoaded(); await OnLoaded();
IsUninstalling = false; IsUninstalling = false;

15
StabilityMatrix/ViewModels/SettingsViewModel.cs

@ -74,13 +74,13 @@ public partial class SettingsViewModel : ObservableObject
partial void OnWebApiHostChanged(string? value) partial void OnWebApiHostChanged(string? value)
{ {
settingsManager.SetWebApiHost(value); settingsManager.Transaction(s => s.WebApiHost = value);
a3WebApiManager.ResetClient(); a3WebApiManager.ResetClient();
} }
partial void OnWebApiPortChanged(string? value) partial void OnWebApiPortChanged(string? value)
{ {
settingsManager.SetWebApiPort(value); settingsManager.Transaction(s => s.WebApiPort = value);
a3WebApiManager.ResetClient(); a3WebApiManager.ResetClient();
} }
@ -90,7 +90,7 @@ public partial class SettingsViewModel : ObservableObject
{ {
if (value != settingsManager.Settings.KeepFolderLinksOnShutdown) if (value != settingsManager.Settings.KeepFolderLinksOnShutdown)
{ {
settingsManager.SetKeepFolderLinksOnShutdown(value); settingsManager.Transaction(s => s.KeepFolderLinksOnShutdown = value);
} }
} }
@ -142,13 +142,14 @@ public partial class SettingsViewModel : ObservableObject
partial void OnSelectedThemeChanged(string value) partial void OnSelectedThemeChanged(string value)
{ {
settingsManager.SetTheme(value); using var st = settingsManager.BeginTransaction();
st.Settings.Theme = value;
ApplyTheme(value); ApplyTheme(value);
} }
partial void OnWindowBackdropTypeChanged(WindowBackdropType oldValue, WindowBackdropType newValue) partial void OnWindowBackdropTypeChanged(WindowBackdropType oldValue, WindowBackdropType newValue)
{ {
settingsManager.SetWindowBackdropType(newValue); settingsManager.Transaction(s => s.WindowBackdropType = newValue);
if (Application.Current.MainWindow != null) if (Application.Current.MainWindow != null)
{ {
WindowBackdrop.ApplyBackdrop(Application.Current.MainWindow, newValue); WindowBackdrop.ApplyBackdrop(Application.Current.MainWindow, newValue);
@ -221,13 +222,15 @@ public partial class SettingsViewModel : ObservableObject
{ {
Id = Guid.NewGuid(), Id = Guid.NewGuid(),
DisplayName = Path.GetFileName(path), DisplayName = Path.GetFileName(path),
#pragma warning disable CS0618 // Type or member is obsolete
Path = path, Path = path,
#pragma warning restore CS0618 // Type or member is obsolete
PackageName = "dank-diffusion", PackageName = "dank-diffusion",
PackageVersion = "v1.0.0", PackageVersion = "v1.0.0",
}; };
// Add package to settings // Add package to settings
settingsManager.AddInstalledPackage(package); settingsManager.Transaction(s => s.InstalledPackages.Add(package));
}); });
// Debug card commands // Debug card commands

Loading…
Cancel
Save