Browse Source

Add Installed package extension managing

pull/438/head
ionite34 10 months ago
parent
commit
e27a00b814
No known key found for this signature in database
GPG Key ID: B3404C5F3827849B
  1. 11
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  2. 183
      StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs
  3. 195
      StabilityMatrix.Avalonia/Views/PackageManager/PackageExtensionBrowserView.axaml
  4. 11
      StabilityMatrix.Avalonia/Views/PackageManager/PackageExtensionBrowserView.axaml.cs
  5. 29
      StabilityMatrix.Core/Models/GitVersion.cs
  6. 34
      StabilityMatrix.Core/Models/Packages/Extensions/InstalledPackageExtension.cs
  7. 5
      StabilityMatrix.Core/Models/Packages/Extensions/PackageExtensionVersion.cs

11
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -37,6 +37,7 @@ using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api; using StabilityMatrix.Core.Models.Api;
using StabilityMatrix.Core.Models.Api.Comfy; using StabilityMatrix.Core.Models.Api.Comfy;
using StabilityMatrix.Core.Models.Database; using StabilityMatrix.Core.Models.Database;
using StabilityMatrix.Core.Models.FileInterfaces;
using StabilityMatrix.Core.Models.PackageModification; using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Models.Packages; using StabilityMatrix.Core.Models.Packages;
using StabilityMatrix.Core.Models.Packages.Extensions; using StabilityMatrix.Core.Models.Packages.Extensions;
@ -487,6 +488,7 @@ public static class DesignData
DialogFactory.Get<PackageExtensionBrowserViewModel>(vm => DialogFactory.Get<PackageExtensionBrowserViewModel>(vm =>
{ {
vm.AddExtensions( vm.AddExtensions(
[
new PackageExtension new PackageExtension
{ {
Author = "123", Author = "123",
@ -495,6 +497,15 @@ public static class DesignData
Reference = new Uri("https://github.com/LykosAI/StabilityMatrix"), Reference = new Uri("https://github.com/LykosAI/StabilityMatrix"),
Files = [new Uri("https://github.com/LykosAI/StabilityMatrix")] Files = [new Uri("https://github.com/LykosAI/StabilityMatrix")]
} }
],
[
new InstalledPackageExtension
{
GitRepositoryUrl = "https://github.com/LykosAI/StabilityMatrix",
Paths = [new DirectoryPath("example-dir")]
},
new InstalledPackageExtension { Paths = [new DirectoryPath("example-dir-2")] }
]
); );
}); });

183
StabilityMatrix.Avalonia/ViewModels/PackageManager/PackageExtensionBrowserViewModel.cs

@ -1,7 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
@ -9,6 +11,8 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using StabilityMatrix.Avalonia.Collections;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views.PackageManager; using StabilityMatrix.Avalonia.Views.PackageManager;
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
@ -23,15 +27,12 @@ namespace StabilityMatrix.Avalonia.ViewModels.PackageManager;
[View(typeof(PackageExtensionBrowserView))] [View(typeof(PackageExtensionBrowserView))]
[Transient] [Transient]
[ManagedService] [ManagedService]
public partial class PackageExtensionBrowserViewModel : ViewModelBase public partial class PackageExtensionBrowserViewModel : ViewModelBase, IDisposable
{ {
public PackagePair? PackagePair { get; set; } private readonly INotificationService notificationService;
private readonly IDisposable cleanUp;
[ObservableProperty]
private string searchFilter = string.Empty;
[ObservableProperty] public PackagePair? PackagePair { get; set; }
private int selectedItemsCount;
[ObservableProperty] [ObservableProperty]
private bool isLoading; private bool isLoading;
@ -39,67 +40,94 @@ public partial class PackageExtensionBrowserViewModel : ViewModelBase
private SourceCache<PackageExtension, string> availableExtensionsSource = private SourceCache<PackageExtension, string> availableExtensionsSource =
new(ext => ext.Author + ext.Title + ext.Reference); new(ext => ext.Author + ext.Title + ext.Reference);
public IObservableCollection<SelectableItem<PackageExtension>> AvailableItems { get; } =
new ObservableCollectionExtended<SelectableItem<PackageExtension>>();
public IObservableCollection<SelectableItem<PackageExtension>> AvailableItemsFiltered { get; } =
new ObservableCollectionExtended<SelectableItem<PackageExtension>>();
public IObservableCollection<SelectableItem<PackageExtension>> SelectedAvailableItems { get; } = public IObservableCollection<SelectableItem<PackageExtension>> SelectedAvailableItems { get; } =
new ObservableCollectionExtended<SelectableItem<PackageExtension>>(); new ObservableCollectionExtended<SelectableItem<PackageExtension>>();
public SearchCollection<
SelectableItem<PackageExtension>,
string,
string
> AvailableItemsSearchCollection { get; }
private SourceCache<InstalledPackageExtension, string> installedExtensionsSource = private SourceCache<InstalledPackageExtension, string> installedExtensionsSource =
new( new(
ext => ext =>
ext.Paths.FirstOrDefault()?.ToString() ?? ext.GitRepositoryUrl ?? ext.GetHashCode().ToString() ext.Paths.FirstOrDefault()?.ToString() ?? ext.GitRepositoryUrl ?? ext.GetHashCode().ToString()
); );
public IObservableCollection<SelectableItem<InstalledPackageExtension>> SelectedInstalledItems { get; } =
new ObservableCollectionExtended<SelectableItem<InstalledPackageExtension>>();
public SearchCollection<
SelectableItem<InstalledPackageExtension>,
string,
string
> InstalledItemsSearchCollection { get; }
public IObservableCollection<InstalledPackageExtension> InstalledExtensions { get; } = public IObservableCollection<InstalledPackageExtension> InstalledExtensions { get; } =
new ObservableCollectionExtended<InstalledPackageExtension>(); new ObservableCollectionExtended<InstalledPackageExtension>();
public PackageExtensionBrowserViewModel() public PackageExtensionBrowserViewModel(INotificationService notificationService)
{ {
var searchPredicate = this.WhenPropertyChanged(vm => vm.SearchFilter) this.notificationService = notificationService;
.Select(
change =>
string.IsNullOrWhiteSpace(change.Value)
? _ => true
: new Func<SelectableItem<PackageExtension>, bool>(
ext => ext.Item.Title.Contains(change.Value, StringComparison.OrdinalIgnoreCase)
)
)
.AsObservable();
var availableItemsChangeSet = availableExtensionsSource var availableItemsChangeSet = availableExtensionsSource
.Connect() .Connect()
.Transform(ext => new SelectableItem<PackageExtension>(ext)) .Transform(ext => new SelectableItem<PackageExtension>(ext))
.Publish(); .Publish();
availableItemsChangeSet
.SortBy(x => x.Item.Title)
.Bind(AvailableItems)
.Filter(searchPredicate)
.Bind(AvailableItemsFiltered)
.Subscribe();
availableItemsChangeSet availableItemsChangeSet
.AutoRefresh(item => item.IsSelected) .AutoRefresh(item => item.IsSelected)
.Filter(item => item.IsSelected) .Filter(item => item.IsSelected)
.ForEachChange(OnSelectedItemsUpdate)
.Bind(SelectedAvailableItems) .Bind(SelectedAvailableItems)
.Subscribe(); .Subscribe();
availableItemsChangeSet.Connect(); var installedItemsChangeSet = installedExtensionsSource
.Connect()
.Transform(ext => new SelectableItem<InstalledPackageExtension>(ext))
.Publish();
installedItemsChangeSet
.AutoRefresh(item => item.IsSelected)
.Filter(item => item.IsSelected)
.Bind(SelectedInstalledItems)
.Subscribe();
SelectedAvailableItems cleanUp = new CompositeDisposable(
.WhenPropertyChanged(x => x.Count) AvailableItemsSearchCollection = new SearchCollection<
.Select(x => x.Value) SelectableItem<PackageExtension>,
.Subscribe(x => SelectedItemsCount = x); string,
string
>(
availableItemsChangeSet,
query =>
string.IsNullOrWhiteSpace(query)
? _ => true
: x => x.Item.Title.Contains(query, StringComparison.OrdinalIgnoreCase)
),
availableItemsChangeSet.Connect(),
InstalledItemsSearchCollection = new SearchCollection<
SelectableItem<InstalledPackageExtension>,
string,
string
>(
installedItemsChangeSet,
query =>
string.IsNullOrWhiteSpace(query)
? _ => true
: x => x.Item.Title.Contains(query, StringComparison.OrdinalIgnoreCase)
),
installedItemsChangeSet.Connect()
);
} }
public void AddExtensions(params PackageExtension[] packageExtensions) public void AddExtensions(
IEnumerable<PackageExtension> packageExtensions,
IEnumerable<InstalledPackageExtension> installedExtensions
)
{ {
availableExtensionsSource.AddOrUpdate(packageExtensions); availableExtensionsSource.AddOrUpdate(packageExtensions);
installedExtensionsSource.AddOrUpdate(installedExtensions);
} }
[RelayCommand] [RelayCommand]
@ -156,23 +184,34 @@ public partial class PackageExtensionBrowserViewModel : ViewModelBase
{ {
if (Design.IsDesignMode) if (Design.IsDesignMode)
{ {
var (availableExtensions, installedExtensions) = SynchronizeExtensions(
availableExtensionsSource.Items,
installedExtensionsSource.Items
);
availableExtensionsSource.EditDiff(availableExtensions);
installedExtensionsSource.EditDiff(installedExtensions);
await Task.Delay(250); await Task.Delay(250);
} }
else else
{ {
// Refresh installed var availableExtensions = await extensionManager.GetManifestExtensionsAsync(
extensionManager.GetManifests(PackagePair.InstalledPackage)
);
var installedExtensions = await extensionManager.GetInstalledExtensionsAsync( var installedExtensions = await extensionManager.GetInstalledExtensionsAsync(
PackagePair.InstalledPackage PackagePair.InstalledPackage
); );
installedExtensionsSource.EditDiff(installedExtensions); // Synchronize
(availableExtensions, installedExtensions) = SynchronizeExtensions(
// Refresh available availableExtensions,
var extensions = await extensionManager.GetManifestExtensionsAsync( installedExtensions
extensionManager.GetManifests(PackagePair.InstalledPackage)
); );
availableExtensionsSource.EditDiff(extensions); availableExtensionsSource.EditDiff(availableExtensions);
installedExtensionsSource.EditDiff(installedExtensions);
} }
} }
finally finally
@ -187,10 +226,58 @@ public partial class PackageExtensionBrowserViewModel : ViewModelBase
{ {
item.IsSelected = false; item.IsSelected = false;
} }
foreach (var item in SelectedInstalledItems.ToImmutableArray())
{
item.IsSelected = false;
}
}
[Pure]
private static (
IEnumerable<PackageExtension>,
IEnumerable<InstalledPackageExtension>
) SynchronizeExtensions(
IEnumerable<PackageExtension> extensions,
IEnumerable<InstalledPackageExtension> installedExtensions
)
{
using var _ = CodeTimer.StartDebug();
var extensionsArr = extensions.ToImmutableArray();
// For extensions, map their file paths for lookup
var repoToExtension = extensionsArr
.SelectMany(ext => ext.Files.Select(path => (path, ext)))
.ToLookup(kv => kv.path.ToString().StripEnd(".git"))
.ToDictionary(group => group.Key, x => x.First().ext);
// For installed extensions, add remote repo if available
var installedExtensionsWithDefinition = installedExtensions.Select(
installedExt =>
installedExt.GitRepositoryUrl is null
|| !repoToExtension.TryGetValue(
installedExt.GitRepositoryUrl.StripEnd(".git"),
out var mappedExt
)
? installedExt
: installedExt with
{
Definition = mappedExt
}
);
return (extensionsArr, installedExtensionsWithDefinition);
} }
private void OnSelectedItemsUpdate(Change<SelectableItem<PackageExtension>, string> change) /// <inheritdoc />
public void Dispose()
{ {
Debug.WriteLine($"{change}"); availableExtensionsSource.Dispose();
installedExtensionsSource.Dispose();
cleanUp.Dispose();
GC.SuppressFinalize(this);
} }
} }

195
StabilityMatrix.Avalonia/Views/PackageManager/PackageExtensionBrowserView.axaml

@ -14,6 +14,7 @@
xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia" xmlns:fluentIcons="clr-namespace:FluentIcons.FluentAvalonia;assembly=FluentIcons.FluentAvalonia"
xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia" xmlns:sg="clr-namespace:SpacedGridControl.Avalonia;assembly=SpacedGridControl.Avalonia"
xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core" xmlns:models="clr-namespace:StabilityMatrix.Core.Models;assembly=StabilityMatrix.Core"
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
d:DataContext="{x:Static mocks:DesignData.PackageExtensionBrowserViewModel}" d:DataContext="{x:Static mocks:DesignData.PackageExtensionBrowserViewModel}"
x:DataType="packageManager:PackageExtensionBrowserViewModel" x:DataType="packageManager:PackageExtensionBrowserViewModel"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
@ -21,6 +22,7 @@
<controls:UserControlBase.Resources> <controls:UserControlBase.Resources>
<!--<input:StandardUICommand x:Key="InstallExtensionCommand" <!--<input:StandardUICommand x:Key="InstallExtensionCommand"
Command="{Binding InstallExtensionCommand}" />--> Command="{Binding InstallExtensionCommand}" />-->
<converters:FileUriConverter x:Key="FileUriConverter"/>
<DataTemplate x:Key="PackageExtensionTemplate"> <DataTemplate x:Key="PackageExtensionTemplate">
<DataTemplate.DataType> <DataTemplate.DataType>
@ -68,17 +70,70 @@
Margin="0,0,16,0" Margin="0,0,16,0"
Grid.Column="2"/> Grid.Column="2"/>
<!--<fluentIcons:SymbolIcon Grid.Column="2" </sg:SpacedGrid>
HorizontalAlignment="Right" </controls:Card>
</DataTemplate>
<DataTemplate x:Key="InstalledPackageExtensionTemplate">
<DataTemplate.DataType>
<x:Type TypeName="models:SelectableItem" x:TypeArguments="extensions:InstalledPackageExtension"/>
</DataTemplate.DataType>
<controls:Card
Padding="12,12"
Margin="4"
Classes="transparent-full"
CornerRadius="8">
<sg:SpacedGrid ColumnDefinitions="*,Auto">
<StackPanel
DataContext="{Binding Item}"
Orientation="Vertical" Spacing="4">
<TextBlock
Text="{Binding Title}"
FontWeight="Light"
FontSize="16"/>
<TextBlock Text="{Binding Version, FallbackValue=''}"
Foreground="{DynamicResource TextFillColorTertiaryBrush}"
FontSize="13" />
<TextBlock TextWrapping="Wrap"
Text="{Binding Definition.Description, FallbackValue=''}"
Foreground="{DynamicResource TextFillColorSecondary}"
FontWeight="Light"
FontSize="13" />
<StackPanel Orientation="Horizontal" Spacing="2">
<controls:HyperlinkIconButton
IsEnabled="{Binding Definition, Converter={x:Static ObjectConverters.IsNotNull}}"
Margin="0"
Padding="0,2"
NavigateUri="{Binding Definition.Reference, FallbackValue=''}"
Content="GitHub"/>
<Ellipse
Width="3"
Height="3"
Opacity="0.8"
VerticalAlignment="Center" VerticalAlignment="Center"
FontSize="24" Fill="{DynamicResource TextFillColorTertiaryBrush}" />
Symbol="ChevronRight" />-->
<controls:HyperlinkIconButton
IsEnabled="{Binding Definition, Converter={x:Static ObjectConverters.IsNotNull}}"
Margin="0"
Padding="0,2"
NavigateUri="{Binding PrimaryPath.FullPath, Converter={StaticResource FileUriConverter}}"
Content="{Binding DisplayPath}"/>
</StackPanel>
</StackPanel>
<CheckBox
Grid.Column="1"
HorizontalAlignment="Right"
IsChecked="{Binding IsSelected}"
Margin="0,0,16,0"/>
</sg:SpacedGrid> </sg:SpacedGrid>
</controls:Card> </controls:Card>
</DataTemplate> </DataTemplate>
</controls:UserControlBase.Resources> </controls:UserControlBase.Resources>
<TabControl TabStripPlacement="Top"> <TabControl TabStripPlacement="Top" SelectionChanged="TabControl_OnSelectionChanged">
<TabControl.Items> <TabControl.Items>
<TabItem FontSize="18" Header="Available Extensions" Margin="8,0,0,0"> <TabItem FontSize="18" Header="Available Extensions" Margin="8,0,0,0">
<sg:SpacedGrid <sg:SpacedGrid
@ -89,19 +144,20 @@
ColumnDefinitions="*,Auto,Auto" ColumnDefinitions="*,Auto,Auto"
ColumnSpacing="8"> ColumnSpacing="8">
<TextBox Margin="4, 0,0,0" <TextBox Margin="4, 0,0,0"
DataContext="{Binding AvailableItemsSearchCollection}"
Watermark="{x:Static lang:Resources.Action_Search}" Watermark="{x:Static lang:Resources.Action_Search}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
Text="{Binding SearchFilter, Mode=TwoWay}"> Text="{Binding Query, Mode=TwoWay}">
<TextBox.InnerRightContent> <TextBox.InnerRightContent>
<Grid> <Grid>
<Button Classes="transparent-full" <Button Classes="transparent-full"
IsVisible="{Binding SearchFilter.Length}" IsVisible="{Binding Query.Length}"
Command="{Binding $parent[TextBox].Clear}"> Command="{Binding ClearQuery}">
<ui:SymbolIcon Symbol="Cancel" /> <ui:SymbolIcon Symbol="Cancel" />
</Button> </Button>
<ui:SymbolIcon <ui:SymbolIcon
IsVisible="{Binding !SearchFilter.Length}" IsVisible="{Binding !Query.Length}"
Margin="0,0,10,0" Margin="0,0,10,0"
FontSize="16" FontSize="16"
Symbol="Find" /> Symbol="Find" />
@ -133,19 +189,31 @@
<ScrollViewer Grid.Row="1"> <ScrollViewer Grid.Row="1">
<ItemsRepeater <ItemsRepeater
DataContext="{Binding AvailableItemsSearchCollection}"
ItemTemplate="{StaticResource PackageExtensionTemplate}" ItemTemplate="{StaticResource PackageExtensionTemplate}"
ItemsSource="{Binding AvailableItemsFiltered}"> ItemsSource="{Binding FilteredItems}">
<ItemsRepeater.Layout> <ItemsRepeater.Layout>
<StackLayout Orientation="Vertical" /> <StackLayout Orientation="Vertical" />
</ItemsRepeater.Layout> </ItemsRepeater.Layout>
</ItemsRepeater> </ItemsRepeater>
</ScrollViewer> </ScrollViewer>
<Panel
IsVisible="{Binding IsLoading}"
Grid.Row="1">
<ProgressBar
Margin="32,16"
IsIndeterminate="True"
IsEnabled="{Binding IsLoading}"
IsVisible="{Binding IsLoading}"/>
</Panel>
<ui:InfoBar <ui:InfoBar
Grid.Row="2" Grid.Row="2"
IsOpen="{Binding !!SelectedAvailableItems.Count}" IsOpen="{Binding !!SelectedAvailableItems.Count}"
HorizontalContentAlignment="Right" HorizontalContentAlignment="Right"
HorizontalAlignment="Right" HorizontalAlignment="Right"
CloseButtonCommand="{Binding ClearSelection}"
IsIconVisible="False"> IsIconVisible="False">
<ui:InfoBar.Title> <ui:InfoBar.Title>
<MultiBinding StringFormat="{}{0} Selected"> <MultiBinding StringFormat="{}{0} Selected">
@ -163,53 +231,108 @@
</sg:SpacedGrid> </sg:SpacedGrid>
</TabItem> </TabItem>
<TabItem FontSize="18" Header="Installed Extensions" Margin="0,0,0,0"/> <TabItem FontSize="18" Header="Installed Extensions" Margin="0,0,0,0">
<!--<TabItem Header="Training"> <sg:SpacedGrid
<ScrollViewer Padding="8"> RowDefinitions="Auto,*,Auto"
<StackPanel Orientation="Vertical"> RowSpacing="12"
<Grid ColumnDefinitions="*, Auto" Margin="0,0,0,8"> Margin="8">
<sg:SpacedGrid
ColumnDefinitions="*,Auto,Auto"
ColumnSpacing="8">
<TextBox Margin="4, 0,0,0" <TextBox Margin="4, 0,0,0"
DataContext="{Binding InstalledItemsSearchCollection}"
Watermark="{x:Static lang:Resources.Action_Search}" Watermark="{x:Static lang:Resources.Action_Search}"
Height="36"
FontSize="16"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Center" VerticalAlignment="Center"
KeyDown="InputElement_OnKeyDown" Text="{Binding Query, Mode=TwoWay}">
Text="{Binding SearchFilter, Mode=TwoWay}">
<TextBox.InnerRightContent> <TextBox.InnerRightContent>
<Grid> <Grid>
<Button Classes="transparent-full" <Button Classes="transparent-full"
IsVisible="{Binding SearchFilter.Length}" IsVisible="{Binding Query.Length}"
Command="{Binding ClearSearchQuery}"> Command="{Binding ClearQuery}">
<controls1:SymbolIcon Symbol="Cancel" /> <ui:SymbolIcon Symbol="Cancel" />
</Button> </Button>
<controls1:SymbolIcon <ui:SymbolIcon
IsVisible="{Binding !SearchFilter.Length}" IsVisible="{Binding !Query.Length}"
Margin="0,0,10,0" Margin="0,0,10,0"
FontSize="16" FontSize="16"
Symbol="Find" /> Symbol="Find" />
</Grid> </Grid>
</TextBox.InnerRightContent> </TextBox.InnerRightContent>
</TextBox> </TextBox>
<ToggleSwitch Grid.Column="1" OnContent="Show All Packages" OffContent="Show All Packages"
IsChecked="{Binding ShowIncompatiblePackages}" <!-- Refresh Button -->
VerticalAlignment="Center" <Button
Grid.Row="0"
Grid.Column="1"
Padding="16,6"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Margin="16, 0, 8, 0" VerticalAlignment="Stretch"
ToolTip.Tip="Enabling &quot;Show All Packages&quot; will include packages that may not be compatible with your system and may run very slowly as a result." /> Command="{Binding RefreshCommand}">
</Grid> <ui:SymbolIcon FontSize="16" Symbol="Refresh" />
<ItemsRepeater ItemsSource="{Binding InstalledExtensions}"> </Button>
<!-- Settings / Edit Manifests Button -->
<Button
Grid.Row="0"
Grid.Column="2"
Padding="16,6"
HorizontalAlignment="Left"
VerticalAlignment="Stretch">
<ui:SymbolIcon FontSize="16" Symbol="Settings" />
</Button>
</sg:SpacedGrid>
<ScrollViewer Grid.Row="1">
<ItemsRepeater
DataContext="{Binding InstalledItemsSearchCollection}"
ItemTemplate="{StaticResource InstalledPackageExtensionTemplate}"
ItemsSource="{Binding FilteredItems}">
<ItemsRepeater.Layout> <ItemsRepeater.Layout>
<StackLayout Orientation="Vertical" /> <StackLayout Orientation="Vertical" />
</ItemsRepeater.Layout> </ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<StaticResource ResourceKey="PackageExtensionTemplate" />
</ItemsRepeater.ItemTemplate>
</ItemsRepeater> </ItemsRepeater>
</StackPanel>
</ScrollViewer> </ScrollViewer>
</TabItem>-->
<Panel
IsVisible="{Binding IsLoading}"
Grid.Row="1"
>
<ProgressBar
Margin="32,16"
IsIndeterminate="True"
IsEnabled="{Binding IsLoading}"
IsVisible="{Binding IsLoading}"/>
</Panel>
<ui:InfoBar
Grid.Row="2"
IsOpen="{Binding !!SelectedInstalledItems.Count}"
HorizontalContentAlignment="Right"
HorizontalAlignment="Right"
CloseButtonCommand="{Binding ClearSelection}"
IsIconVisible="False">
<ui:InfoBar.Title>
<MultiBinding StringFormat="{}{0} Selected">
<Binding Path="SelectedInstalledItems.Count"/>
</MultiBinding>
</ui:InfoBar.Title>
<ui:InfoBar.ActionButton>
<StackPanel Orientation="Horizontal" Spacing="6">
<Button
Classes="accent"
Content="{x:Static lang:Resources.Action_Update}"/>
<Button
Content="{x:Static lang:Resources.Action_Uninstall}"/>
</StackPanel>
</ui:InfoBar.ActionButton>
</ui:InfoBar>
</sg:SpacedGrid>
</TabItem>
</TabControl.Items> </TabControl.Items>
</TabControl> </TabControl>
</controls:UserControlBase> </controls:UserControlBase>

11
StabilityMatrix.Avalonia/Views/PackageManager/PackageExtensionBrowserView.axaml.cs

@ -1,4 +1,6 @@
using Avalonia.Controls;
using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.ViewModels.PackageManager;
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Views.PackageManager; namespace StabilityMatrix.Avalonia.Views.PackageManager;
@ -10,4 +12,13 @@ public partial class PackageExtensionBrowserView : UserControlBase
{ {
InitializeComponent(); InitializeComponent();
} }
private void TabControl_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
// Clear selection when switching tabs
if (DataContext is PackageExtensionBrowserViewModel vm)
{
vm.ClearSelection();
}
}
} }

29
StabilityMatrix.Core/Models/GitVersion.cs

@ -3,11 +3,38 @@ namespace StabilityMatrix.Core.Models;
/// <summary> /// <summary>
/// Union of either Tag or Branch + CommitSha. /// Union of either Tag or Branch + CommitSha.
/// </summary> /// </summary>
public record GitVersion public record GitVersion : IFormattable
{ {
public string? Tag { get; init; } public string? Tag { get; init; }
public string? Branch { get; init; } public string? Branch { get; init; }
public string? CommitSha { get; init; } public string? CommitSha { get; init; }
/// <inheritdoc />
public override string ToString()
{
if (!string.IsNullOrEmpty(Tag))
{
return Tag;
}
if (!string.IsNullOrEmpty(Branch) && !string.IsNullOrEmpty(CommitSha))
{
return $"{Branch}@{CommitSha[..7]}";
}
if (!string.IsNullOrEmpty(Branch))
{
return Branch;
}
return !string.IsNullOrEmpty(CommitSha) ? CommitSha[..7] : "";
}
/// <inheritdoc />
public string ToString(string? format, IFormatProvider? formatProvider)
{
return ToString();
}
} }

34
StabilityMatrix.Core/Models/Packages/Extensions/InstalledPackageExtension.cs

@ -9,6 +9,11 @@ public record InstalledPackageExtension
/// </summary> /// </summary>
public required IEnumerable<IPathObject> Paths { get; init; } public required IEnumerable<IPathObject> Paths { get; init; }
/// <summary>
/// Primary path of the extension.
/// </summary>
public IPathObject? PrimaryPath => Paths.FirstOrDefault();
/// <summary> /// <summary>
/// The version of the extension. /// The version of the extension.
/// </summary> /// </summary>
@ -23,4 +28,33 @@ public record InstalledPackageExtension
/// The PackageExtension definition, if available. /// The PackageExtension definition, if available.
/// </summary> /// </summary>
public PackageExtension? Definition { get; init; } public PackageExtension? Definition { get; init; }
public string Title
{
get
{
if (Definition?.Title is { } title)
{
return title;
}
if (Paths.FirstOrDefault()?.Name is { } pathName)
{
return pathName;
}
return "";
}
}
/// <summary>
/// Path containing PrimaryPath and its parent.
/// </summary>
public string DisplayPath =>
PrimaryPath switch
{
null => "",
DirectoryPath { Parent: { } parentDir } dir => $"{parentDir.Name}/{dir.Name}",
_ => PrimaryPath.Name
};
} }

5
StabilityMatrix.Core/Models/Packages/Extensions/PackageExtensionVersion.cs

@ -1,3 +1,6 @@
namespace StabilityMatrix.Core.Models.Packages.Extensions; namespace StabilityMatrix.Core.Models.Packages.Extensions;
public record PackageExtensionVersion : GitVersion; public record PackageExtensionVersion : GitVersion
{
public override string ToString() => base.ToString();
};

Loading…
Cancel
Save