Browse Source

Add importing for unknown packages found in index

pull/109/head
Ionite 1 year ago
parent
commit
6fbd07edee
No known key found for this signature in database
  1. 15
      StabilityMatrix.Avalonia/App.axaml.cs
  2. 21
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  3. 2
      StabilityMatrix.Avalonia/Extensions/DirectoryPathExtensions.cs
  4. 63
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  5. 21
      StabilityMatrix.Avalonia/Languages/Resources.resx
  6. 2
      StabilityMatrix.Avalonia/Services/ServiceManager.cs
  7. 1
      StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
  8. 221
      StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs
  9. 70
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs
  10. 118
      StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs
  11. 51
      StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml
  12. 17
      StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml.cs
  13. 55
      StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml
  14. 4
      StabilityMatrix.Core/Models/Packages/BasePackage.cs
  15. 105
      StabilityMatrix.Core/Models/Packages/UnknownPackage.cs
  16. 17
      StabilityMatrix.Core/Models/UnknownInstalledPackage.cs

15
StabilityMatrix.Avalonia/App.axaml.cs

@ -247,14 +247,16 @@ public sealed class App : Application
services.AddTransient<LaunchOptionsViewModel>();
services.AddTransient<ExceptionViewModel>();
services.AddTransient<EnvVarsViewModel>();
services.AddTransient<PackageImportViewModel>();
// Dialog view models (singleton)
services.AddSingleton<FirstLaunchSetupViewModel>();
services.AddSingleton<UpdateViewModel>();
// Other transients (usually sub view models)
services.AddTransient<CheckpointFolder>()
.AddTransient<CheckpointFile>()
.AddTransient<CheckpointBrowserCardViewModel>();
services.AddTransient<CheckpointFolder>();
services.AddTransient<CheckpointFile>();
services.AddTransient<CheckpointBrowserCardViewModel>();
services.AddTransient<PackageCardViewModel>();
// Global progress
@ -280,7 +282,9 @@ public sealed class App : Application
.Register(provider.GetRequiredService<ExceptionViewModel>)
.Register(provider.GetRequiredService<EnvVarsViewModel>)
.Register(provider.GetRequiredService<ProgressManagerViewModel>)
.Register(provider.GetRequiredService<FirstLaunchSetupViewModel>));
.Register(provider.GetRequiredService<FirstLaunchSetupViewModel>)
.Register(provider.GetRequiredService<PackageImportViewModel>)
);
}
internal static void ConfigureViews(IServiceCollection services)
@ -300,6 +304,7 @@ public sealed class App : Application
services.AddTransient<UpdateDialog>();
services.AddTransient<ExceptionDialog>();
services.AddTransient<EnvVarsDialog>();
services.AddTransient<PackageImportDialog>();
// Controls
services.AddTransient<RefreshBadge>();

21
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;
@ -296,10 +293,15 @@ public static class DesignData
{
var settings = Services.GetRequiredService<ISettingsManager>();
var vm = Services.GetRequiredService<PackageManagerViewModel>();
vm.Packages = new ObservableCollection<PackageCardViewModel>(
settings.Settings.InstalledPackages.Select(p =>
DialogFactory.Get<PackageCardViewModel>(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;
}
}
@ -405,6 +407,9 @@ public static class DesignData
};
});
public static PackageImportViewModel PackageImportViewModel =>
DialogFactory.Get<PackageImportViewModel>();
public static RefreshBadgeViewModel RefreshBadgeViewModel => new()
{
State = ProgressState.Success

2
StabilityMatrix.Avalonia/Extensions/DirectoryPathExtensions.cs

@ -69,7 +69,7 @@ public static class DirectoryPathExtensions
{
try
{
filePath.Attributes |= FileAttributes.Normal;
filePath.Attributes = FileAttributes.Normal;
filePath.Delete();
}
catch (IOException ex)

63
StabilityMatrix.Avalonia/Languages/Resources.Designer.cs generated

@ -68,6 +68,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Import.
/// </summary>
public static string Action_Import {
get {
return ResourceManager.GetString("Action_Import", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Launch.
/// </summary>
@ -113,6 +122,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Branches.
/// </summary>
public static string Label_Branches {
get {
return ResourceManager.GetString("Label_Branches", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Language.
/// </summary>
@ -122,6 +140,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Package Type.
/// </summary>
public static string Label_PackageType {
get {
return ResourceManager.GetString("Label_PackageType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Relaunch Required.
/// </summary>
@ -131,6 +158,42 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Releases.
/// </summary>
public static string Label_Releases {
get {
return ResourceManager.GetString("Label_Releases", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unknown Package.
/// </summary>
public static string Label_UnknownPackage {
get {
return ResourceManager.GetString("Label_UnknownPackage", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Version.
/// </summary>
public static string Label_Version {
get {
return ResourceManager.GetString("Label_Version", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Version Type.
/// </summary>
public static string Label_VersionType {
get {
return ResourceManager.GetString("Label_VersionType", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Relaunch is required for new language option to take effect.
/// </summary>

21
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -45,4 +45,25 @@
<data name="Label_RelaunchRequired" xml:space="preserve">
<value>Relaunch Required</value>
</data>
<data name="Label_UnknownPackage" xml:space="preserve">
<value>Unknown Package</value>
</data>
<data name="Action_Import" xml:space="preserve">
<value>Import</value>
</data>
<data name="Label_PackageType" xml:space="preserve">
<value>Package Type</value>
</data>
<data name="Label_Version" xml:space="preserve">
<value>Version</value>
</data>
<data name="Label_VersionType" xml:space="preserve">
<value>Version Type</value>
</data>
<data name="Label_Releases" xml:space="preserve">
<value>Releases</value>
</data>
<data name="Label_Branches" xml:space="preserve">
<value>Branches</value>
</data>
</root>

2
StabilityMatrix.Avalonia/Services/ServiceManager.cs

@ -217,6 +217,8 @@ public class ServiceManager<T>
throw new NullReferenceException($"Unable to create instance for {instance.GetType().FullName}");
}
view.DataContext = instance;
return new BetterContentDialog { Content = view };
}
}

1
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj

@ -25,6 +25,7 @@
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.1" />
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
<PackageReference Include="DynamicData" Version="7.14.2" />
<PackageReference Include="FluentAvaloniaUI" Version="2.0.0" />
<PackageReference Include="FluentIcons.Avalonia" Version="1.1.207" />
<PackageReference Include="FluentIcons.FluentAvalonia" Version="1.1.207" />

221
StabilityMatrix.Avalonia/ViewModels/Dialogs/PackageImportViewModel.cs

@ -0,0 +1,221 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using NLog;
using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels.Dialogs;
[View(typeof(PackageImportDialog))]
public partial class PackageImportViewModel : ContentDialogViewModelBase
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
private readonly IPackageFactory packageFactory;
private readonly ISettingsManager settingsManager;
[ObservableProperty] private DirectoryPath? packagePath;
[ObservableProperty] private BasePackage? selectedBasePackage;
public IReadOnlyList<BasePackage> AvailablePackages
=> packageFactory.GetAllAvailablePackages().ToImmutableArray();
[ObservableProperty] private PackageVersion? selectedVersion;
[ObservableProperty] private ObservableCollection<GitCommit>? availableCommits;
[ObservableProperty] private ObservableCollection<PackageVersion>? availableVersions;
[ObservableProperty] private GitCommit? selectedCommit;
// Version types (release or commit)
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(ReleaseLabelText),
nameof(IsReleaseMode), nameof(SelectedVersion))]
private PackageVersionType selectedVersionType = PackageVersionType.Commit;
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(IsReleaseModeAvailable))]
private PackageVersionType availableVersionTypes =
PackageVersionType.GithubRelease | PackageVersionType.Commit;
public string ReleaseLabelText => IsReleaseMode ? "Version" : "Branch";
public bool IsReleaseMode
{
get => SelectedVersionType == PackageVersionType.GithubRelease;
set => SelectedVersionType = value ? PackageVersionType.GithubRelease : PackageVersionType.Commit;
}
public bool IsReleaseModeAvailable => AvailableVersionTypes.HasFlag(PackageVersionType.GithubRelease);
public PackageImportViewModel(
IPackageFactory packageFactory,
ISettingsManager settingsManager)
{
this.packageFactory = packageFactory;
this.settingsManager = settingsManager;
}
public override async Task OnLoadedAsync()
{
SelectedBasePackage ??= AvailablePackages[0];
if (Design.IsDesignMode) return;
// Populate available versions
try
{
if (IsReleaseMode)
{
var versions = (await SelectedBasePackage.GetAllVersions()).ToList();
AvailableVersions = new ObservableCollection<PackageVersion>(versions);
if (!AvailableVersions.Any()) return;
SelectedVersion = AvailableVersions[0];
}
else
{
var branches = (await SelectedBasePackage.GetAllBranches()).ToList();
AvailableVersions = new ObservableCollection<PackageVersion>(branches.Select(b =>
new PackageVersion
{
TagName = b.Name,
ReleaseNotesMarkdown = b.Commit.Label
}));
UpdateSelectedVersionToLatestMain();
}
}
catch (Exception e)
{
Logger.Warn("Error getting versions: {Exception}", e.ToString());
}
}
private static string GetDisplayVersion(string version, string? branch)
{
return branch == null ? version : $"{branch}@{version[..7]}";
}
// When available version types change, reset selected version type if not compatible
partial void OnAvailableVersionTypesChanged(PackageVersionType value)
{
if (!value.HasFlag(SelectedVersionType))
{
SelectedVersionType = value;
}
}
// When changing branch / release modes, refresh
// ReSharper disable once UnusedParameterInPartialMethod
partial void OnSelectedVersionTypeChanged(PackageVersionType value)
=> OnSelectedBasePackageChanged(SelectedBasePackage);
partial void OnSelectedBasePackageChanged(BasePackage? value)
{
if (value is null || SelectedBasePackage is null)
{
AvailableVersions?.Clear();
AvailableCommits?.Clear();
return;
}
AvailableVersions?.Clear();
AvailableCommits?.Clear();
AvailableVersionTypes = SelectedBasePackage.ShouldIgnoreReleases
? PackageVersionType.Commit
: PackageVersionType.GithubRelease | PackageVersionType.Commit;
if (Design.IsDesignMode) return;
Dispatcher.UIThread.InvokeAsync(async () =>
{
Logger.Debug($"Release mode: {IsReleaseMode}");
var versions = (await value.GetAllVersions(IsReleaseMode)).ToList();
if (!versions.Any()) return;
AvailableVersions = new ObservableCollection<PackageVersion>(versions);
Logger.Debug($"Available versions: {string.Join(", ", AvailableVersions)}");
SelectedVersion = AvailableVersions[0];
if (!IsReleaseMode)
{
var commits = (await value.GetAllCommits(SelectedVersion.TagName))?.ToList();
if (commits is null || commits.Count == 0) return;
AvailableCommits = new ObservableCollection<GitCommit>(commits);
SelectedCommit = AvailableCommits[0];
UpdateSelectedVersionToLatestMain();
}
}).SafeFireAndForget();
}
private void UpdateSelectedVersionToLatestMain()
{
if (AvailableVersions is null)
{
SelectedVersion = null;
}
else
{
// First try to find master
var version = AvailableVersions.FirstOrDefault(x => x.TagName == "master");
// If not found, try main
version ??= AvailableVersions.FirstOrDefault(x => x.TagName == "main");
// If still not found, just use the first one
version ??= AvailableVersions[0];
SelectedVersion = version;
}
}
public void AddPackageWithCurrentInputs()
{
if (SelectedBasePackage is null || PackagePath is null)
return;
string version;
if (IsReleaseMode)
{
version = SelectedVersion?.TagName ??
throw new NullReferenceException("Selected version is null");
}
else
{
version = SelectedCommit?.Sha ??
throw new NullReferenceException("Selected commit is null");
}
var branch = SelectedVersionType == PackageVersionType.GithubRelease ?
null : SelectedVersion!.TagName;
var package = new InstalledPackage
{
Id = Guid.NewGuid(),
DisplayName = PackagePath.Name,
PackageName = SelectedBasePackage.Name,
LibraryPath = $"Packages{Path.DirectorySeparatorChar}{PackagePath.Name}",
PackageVersion = version,
DisplayVersion = GetDisplayVersion(version, branch),
InstalledBranch = branch,
LaunchCommand = SelectedBasePackage.LaunchCommand,
LastUpdateCheck = DateTimeOffset.Now,
};
settingsManager.Transaction(s => s.InstalledPackages.Add(package));
}
}

70
StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageCardViewModel.cs

@ -1,18 +1,22 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Notifications;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
using Microsoft.Extensions.Logging;
using StabilityMatrix.Avalonia.Animations;
using StabilityMatrix.Avalonia.Extensions;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.Dialogs;
using StabilityMatrix.Core.Extensions;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Models.Progress;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
@ -26,24 +30,28 @@ public partial class PackageCardViewModel : ProgressViewModel
private readonly INotificationService notificationService;
private readonly ISettingsManager settingsManager;
private readonly INavigationService navigationService;
private readonly ServiceManager<ViewModelBase> vmFactory;
[ObservableProperty] private InstalledPackage? package;
[ObservableProperty] private Uri? cardImage;
[ObservableProperty] private string? cardImageSource;
[ObservableProperty] private bool isUpdateAvailable;
[ObservableProperty] private string? installedVersion;
[ObservableProperty] private bool isUnknownPackage;
public PackageCardViewModel(
ILogger<PackageCardViewModel> logger,
IPackageFactory packageFactory,
INotificationService notificationService,
ISettingsManager settingsManager,
INavigationService navigationService)
INavigationService navigationService,
ServiceManager<ViewModelBase> vmFactory)
{
this.logger = logger;
this.packageFactory = packageFactory;
this.notificationService = notificationService;
this.settingsManager = settingsManager;
this.navigationService = navigationService;
this.vmFactory = vmFactory;
}
partial void OnPackageChanged(InstalledPackage? value)
@ -51,9 +59,21 @@ public partial class PackageCardViewModel : ProgressViewModel
if (string.IsNullOrWhiteSpace(value?.PackageName))
return;
var basePackage = packageFactory[value.PackageName];
CardImage = basePackage?.PreviewImageUri ?? Assets.NoImage;
InstalledVersion = value.DisplayVersion ?? "Unknown";
if (value.PackageName == UnknownPackage.Key)
{
IsUnknownPackage = true;
CardImageSource = "";
InstalledVersion = "Unknown";
}
else
{
IsUnknownPackage = false;
var basePackage = packageFactory[value.PackageName];
CardImageSource = basePackage?.PreviewImageUri.ToString()
?? Assets.NoImage.ToString();
InstalledVersion = value.DisplayVersion ?? "Unknown";
}
}
public override async Task OnLoadedAsync()
@ -105,11 +125,14 @@ public partial class PackageCardViewModel : ProgressViewModel
notificationService.Show(new Notification("Success",
$"Package {Package.DisplayName} uninstalled",
NotificationType.Success));
settingsManager.Transaction(settings =>
if (!IsUnknownPackage)
{
settings.RemoveInstalledPackageAndUpdateActive(Package);
});
settingsManager.Transaction(settings =>
{
settings.RemoveInstalledPackageAndUpdateActive(Package);
});
}
EventManager.Instance.OnInstalledPackagesChanged();
}
@ -118,7 +141,7 @@ public partial class PackageCardViewModel : ProgressViewModel
public async Task Update()
{
if (Package == null) return;
if (Package is null || IsUnknownPackage) return;
var basePackage = packageFactory[Package.PackageName!];
if (basePackage == null)
@ -191,6 +214,29 @@ public partial class PackageCardViewModel : ProgressViewModel
Text = "";
}
}
public async Task Import()
{
if (!IsUnknownPackage || Design.IsDesignMode) return;
PackageImportViewModel viewModel = null!;
var dialog = vmFactory.GetDialog<PackageImportViewModel>(vm =>
{
vm.PackagePath = new DirectoryPath(Package?.FullPath ?? throw new InvalidOperationException());
viewModel = vm;
});
dialog.PrimaryButtonText = Resources.Action_Import;
dialog.CloseButtonText = Resources.Action_Cancel;
dialog.DefaultButton = ContentDialogButton.Primary;
var result = await dialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
viewModel.AddPackageWithCurrentInputs();
EventManager.Instance.OnInstalledPackagesChanged();
}
}
public async Task OpenFolder()
{
@ -202,7 +248,7 @@ public partial class PackageCardViewModel : ProgressViewModel
private async Task<bool> HasUpdate()
{
if (Package == null)
if (Package == null || IsUnknownPackage || Design.IsDesignMode)
return false;
var basePackage = packageFactory[Package.PackageName!];

118
StabilityMatrix.Avalonia/ViewModels/PackageManagerViewModel.cs

@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using DynamicData;
using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Services;
@ -17,6 +19,8 @@ using StabilityMatrix.Avalonia.Views.Dialogs;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Services;
using Symbol = FluentIcons.Common.Symbol;
using SymbolIconSource = FluentIcons.FluentAvalonia.SymbolIconSource;
@ -34,39 +38,73 @@ public partial class PackageManagerViewModel : PageViewModelBase
private readonly IPackageFactory packageFactory;
private readonly ServiceManager<ViewModelBase> dialogFactory;
public PackageManagerViewModel(ISettingsManager settingsManager, IPackageFactory packageFactory,
ServiceManager<ViewModelBase> dialogFactory)
public override string Title => "Packages";
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.Box, IsFilled = true };
/// <summary>
/// List of installed packages
/// </summary>
private readonly SourceCache<InstalledPackage, Guid> installedPackages = new(p => p.Id);
/// <summary>
/// List of indexed packages without a corresponding installed package
/// </summary>
private readonly SourceCache<InstalledPackage, Guid> unknownInstalledPackages = new(p => p.Id);
public IObservableCollection<InstalledPackage> Packages { get; } =
new ObservableCollectionExtended<InstalledPackage>();
public IObservableCollection<PackageCardViewModel> PackageCards { get; } =
new ObservableCollectionExtended<PackageCardViewModel>();
public PackageManagerViewModel(
ISettingsManager settingsManager,
IPackageFactory packageFactory,
ServiceManager<ViewModelBase> dialogFactory
)
{
this.settingsManager = settingsManager;
this.packageFactory = packageFactory;
this.dialogFactory = dialogFactory;
EventManager.Instance.InstalledPackagesChanged += OnInstalledPackagesChanged;
var installed = installedPackages.Connect();
var unknown = unknownInstalledPackages.Connect();
installed
.Or(unknown)
.DeferUntilLoaded()
.Bind(Packages)
.Transform(p => dialogFactory.Get<PackageCardViewModel>(vm =>
{
vm.Package = p;
vm.OnLoadedAsync().SafeFireAndForget();
}))
.Bind(PackageCards)
.Subscribe();
}
[ObservableProperty] private ObservableCollection<PackageCardViewModel> packages;
public void SetPackages(IEnumerable<InstalledPackage> packages)
{
installedPackages.Edit(s => s.Load(packages));
}
public void SetUnknownPackages(IEnumerable<InstalledPackage> packages)
{
unknownInstalledPackages.Edit(s => s.Load(packages));
}
public override bool CanNavigateNext { get; protected set; } = true;
public override bool CanNavigatePrevious { get; protected set; }
public override string Title => "Packages";
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Box, IsFilled = true};
public override async Task OnLoadedAsync()
{
if (Design.IsDesignMode) return;
var installedPackages = settingsManager.Settings.InstalledPackages;
Packages = new ObservableCollection<PackageCardViewModel>(installedPackages.Select(
package => dialogFactory.Get<PackageCardViewModel>(vm =>
{
vm.Package = package;
return vm;
})));
if (Design.IsDesignMode)
return;
foreach (var package in Packages)
{
await package.OnLoadedAsync();
}
installedPackages.EditDiff(settingsManager.Settings.InstalledPackages, InstalledPackage.Comparer);
var currentUnknown = await Task.Run(IndexUnknownPackages);
unknownInstalledPackages.Edit(s => s.Load(currentUnknown));
}
public async Task ShowInstallDialog()
@ -83,16 +121,40 @@ public partial class PackageManagerViewModel : PageViewModelBase
IsPrimaryButtonEnabled = false,
IsSecondaryButtonEnabled = false,
IsFooterVisible = false,
Content = new InstallerDialog
{
DataContext = viewModel
}
Content = new InstallerDialog { DataContext = viewModel }
};
await dialog.ShowAsync();
await OnLoadedAsync();
}
private IEnumerable<UnknownInstalledPackage> IndexUnknownPackages()
{
var packageDir = new DirectoryPath(settingsManager.LibraryDir).JoinDir("Packages");
if (!packageDir.Exists)
{
yield break;
}
var currentPackages = settingsManager.Settings.InstalledPackages.ToImmutableArray();
foreach (var subDir in packageDir.Info
.EnumerateDirectories()
.Select(info => new DirectoryPath(info)))
{
var expectedLibraryPath = $"Packages{Path.DirectorySeparatorChar}{subDir.Name}";
// Skip if the package is already installed
if (currentPackages.Any(p => p.LibraryPath == expectedLibraryPath))
{
continue;
}
yield return UnknownInstalledPackage.FromDirectoryName(subDir.Name);
}
}
private void OnInstalledPackagesChanged(object? sender, EventArgs e) =>
OnLoadedAsync().SafeFireAndForget();
}

51
StabilityMatrix.Avalonia/Views/Dialogs/PackageImportDialog.axaml

@ -0,0 +1,51 @@
<controls:UserControlBase xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:FluentAvalonia.UI.Controls"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:vmDialogs="clr-namespace:StabilityMatrix.Avalonia.ViewModels.Dialogs"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
d:DataContext="{x:Static mocks:DesignData.PackageImportViewModel}"
x:DataType="vmDialogs:PackageImportViewModel"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="StabilityMatrix.Avalonia.Views.Dialogs.PackageImportDialog">
<StackPanel Margin="8" Spacing="8">
<ui:SettingsExpander Header="{x:Static lang:Resources.Label_PackageType}">
<ui:SettingsExpander.Footer>
<ui:FAComboBox
HorizontalAlignment="Stretch"
ItemsSource="{Binding AvailablePackages}"
DisplayMemberBinding="{Binding DisplayName}"
SelectedItem="{Binding SelectedBasePackage}"/>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander Header="{x:Static lang:Resources.Label_VersionType}">
<ui:SettingsExpander.Footer>
<ToggleSwitch
IsEnabled="{Binding IsReleaseModeAvailable}"
OnContent="{x:Static lang:Resources.Label_Releases}"
OffContent="{x:Static lang:Resources.Label_Branches}"
IsChecked="{Binding IsReleaseMode}"/>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
<ui:SettingsExpander Header="{x:Static lang:Resources.Label_Version}">
<ui:SettingsExpander.Footer>
<StackPanel Orientation="Horizontal" Spacing="8">
<ui:FAComboBox
ItemsSource="{Binding AvailableVersions}"
DisplayMemberBinding="{Binding TagName}"
SelectedItem="{Binding SelectedVersion}"/>
<ui:FAComboBox
IsVisible="{Binding !IsReleaseMode}"
ItemsSource="{Binding AvailableCommits}"
DisplayMemberBinding="{Binding Sha}"
SelectedItem="{Binding SelectedCommit}"/>
</StackPanel>
</ui:SettingsExpander.Footer>
</ui:SettingsExpander>
</StackPanel>
</controls:UserControlBase>

17
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);
}
}

55
StabilityMatrix.Avalonia/Views/PackageManagerPage.axaml

@ -4,11 +4,11 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:packageManager="clr-namespace:StabilityMatrix.Avalonia.ViewModels.PackageManager"
xmlns:faicon="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="viewModels:PackageManagerViewModel"
x:CompileBindings="True"
@ -24,7 +24,7 @@
<!-- Cards -->
<ScrollViewer Grid.Row="1">
<ItemsRepeater
ItemsSource="{Binding Packages}">
ItemsSource="{Binding PackageCards}">
<ItemsRepeater.Layout>
<UniformGridLayout MinColumnSpacing="12" MinRowSpacing="12" />
</ItemsRepeater.Layout>
@ -36,7 +36,7 @@
<TextBlock Grid.Row="0"
Grid.Column="0"
Text="{Binding Package.DisplayName}"
Text="{Binding Package.DisplayName, FallbackValue=''}"
TextAlignment="Left" />
<Button
Grid.Row="0"
@ -51,6 +51,7 @@
<Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedLeft">
<MenuItem Header="Check for Update"
IsVisible="{Binding !IsUnknownPackage}"
Command="{Binding OnLoadedAsync}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Refresh" />
@ -79,6 +80,7 @@
VerticalAlignment="Center"
Text="{Binding InstalledVersion}" />
<!-- Normal packages shows image -->
<controls:BetterAdvancedImage
Grid.Row="2"
Grid.Column="0"
@ -89,12 +91,34 @@
HorizontalAlignment="Center"
VerticalContentAlignment="Top"
HorizontalContentAlignment="Center"
Source="{Binding CardImage}"
Source="{Binding CardImageSource}"
IsVisible="{Binding !IsUnknownPackage}"
Stretch="UniformToFill" />
<!-- Unknown packages panel -->
<Border
Grid.Row="2"
Grid.Column="0"
Margin="0,8,0,8"
Height="160"
Width="200"
CornerRadius="4"
HorizontalAlignment="Center"
IsVisible="{Binding IsUnknownPackage}"
Background="#202020">
<TextBlock
TextAlignment="Center"
VerticalAlignment="Center"
FontSize="18"
TextWrapping="Wrap"
Text="{x:Static lang:Resources.Label_UnknownPackage}"/>
</Border>
<Grid Grid.Row="3" Grid.Column="0"
IsVisible="{Binding IsUpdateAvailable}"
ColumnDefinitions="*, *">
<!-- Launch and update buttons -->
<Button Grid.Column="0" Classes="accent"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
@ -122,14 +146,33 @@
<!-- Big launch button -->
<Button Grid.Row="3" Grid.Column="0" Classes="accent"
VerticalAlignment="Bottom"
IsVisible="{Binding !IsUpdateAvailable}"
Command="{Binding Launch}"
HorizontalAlignment="Stretch">
<Button.IsVisible>
<MultiBinding Converter="{x:Static BoolConverters.And}">
<Binding Path="!IsUpdateAvailable" />
<Binding Path="!IsUnknownPackage" />
</MultiBinding>
</Button.IsVisible>
<StackPanel Orientation="Horizontal" Margin="0,2,0,2">
<faicon:Icon Value="fa-solid fa-rocket"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="Launch" />
<TextBlock Text="{x:Static lang:Resources.Action_Launch}" />
</StackPanel>
</Button>
<!-- Import button (for unknown) -->
<Button Grid.Row="3" Grid.Column="0" Classes="transparent-info"
VerticalAlignment="Bottom"
HorizontalAlignment="Stretch"
Command="{Binding Import}"
IsVisible="{Binding IsUnknownPackage}">
<StackPanel Orientation="Horizontal" Margin="0,2,0,2">
<faicon:Icon Value="fa-solid fa-circle-question"
Margin="0,0,8,0"
FontSize="14" />
<TextBlock Text="{x:Static lang:Resources.Action_Import}" />
</StackPanel>
</Button>

4
StabilityMatrix.Core/Models/Packages/BasePackage.cs

@ -73,8 +73,8 @@ public abstract class BasePackage
public abstract Task<IEnumerable<Branch>> GetAllBranches();
public abstract Task<IEnumerable<Release>> GetAllReleases();
public abstract string DownloadLocation { get; }
public abstract string InstallLocation { get; set; }
public virtual string? DownloadLocation { get; }
public virtual string? InstallLocation { get; set; }
public event EventHandler<ProcessOutput>? ConsoleOutput;
public event EventHandler<int>? Exited;

105
StabilityMatrix.Core/Models/Packages/UnknownPackage.cs

@ -0,0 +1,105 @@
using Octokit;
using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.Progress;
namespace StabilityMatrix.Core.Models.Packages;
public class UnknownPackage : BasePackage
{
public static string Key => "unknown-package";
public override string Name => Key;
public override string DisplayName { get; set; } = "Unknown Package";
public override string Author => "";
public override string GithubUrl => "";
public override string LicenseType => "AGPL-3.0";
public override string LicenseUrl =>
"https://github.com/LykosAI/StabilityMatrix/blob/main/LICENSE";
public override string Blurb => "A dank interface for diffusion";
public override string LaunchCommand => "test";
public override Uri PreviewImageUri => new("");
public override IReadOnlyList<string> ExtraLaunchCommands => new[]
{
"test-config",
};
/// <inheritdoc />
public override Task<string> DownloadPackage(string version, bool isCommitHash, string? branch, IProgress<ProgressReport>? progress = null)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task InstallPackage(IProgress<ProgressReport>? progress = null)
{
throw new NotImplementedException();
}
public override Task RunPackage(string installedPackagePath, string command, string arguments)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task SetupModelFolders(DirectoryPath installDirectory)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task UpdateModelFolders(DirectoryPath installDirectory)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task RemoveModelFolderLinks(DirectoryPath installDirectory)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override void Shutdown()
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task WaitForShutdown()
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task<bool> CheckForUpdates(InstalledPackage package)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task<string> Update(InstalledPackage installedPackage, IProgress<ProgressReport>? progress = null,
bool includePrerelease = false)
{
throw new NotImplementedException();
}
/// <inheritdoc />
public override Task<IEnumerable<Release>> GetReleaseTags() => Task.FromResult(Enumerable.Empty<Release>());
public override List<LaunchOptionDefinition> LaunchOptions => new();
public override Task<string> GetLatestVersion() => Task.FromResult(string.Empty);
public override Task<IEnumerable<PackageVersion>> GetAllVersions(bool isReleaseMode = true) => Task.FromResult(Enumerable.Empty<PackageVersion>());
/// <inheritdoc />
public override Task<IEnumerable<GitCommit>?> GetAllCommits(string branch, int page = 1, int perPage = 10) => Task.FromResult<IEnumerable<GitCommit>?>(null);
/// <inheritdoc />
public override Task<IEnumerable<Branch>> GetAllBranches() => Task.FromResult(Enumerable.Empty<Branch>());
/// <inheritdoc />
public override Task<IEnumerable<Release>> GetAllReleases() => Task.FromResult(Enumerable.Empty<Release>());
}

17
StabilityMatrix.Core/Models/UnknownInstalledPackage.cs

@ -0,0 +1,17 @@
using StabilityMatrix.Core.Models.Packages;
namespace StabilityMatrix.Core.Models;
public class UnknownInstalledPackage : InstalledPackage
{
public static UnknownInstalledPackage FromDirectoryName(string name)
{
return new UnknownInstalledPackage
{
Id = Guid.NewGuid(),
PackageName = UnknownPackage.Key,
DisplayName = name,
LibraryPath = $"Packages{System.IO.Path.DirectorySeparatorChar}{name}",
};
}
}
Loading…
Cancel
Save