Browse Source

task cancellation working & editdiff on UI thread & dynamicdata for categories

pull/629/head
JT 8 months ago
parent
commit
e93a097f71
  1. 6
      CHANGELOG.md
  2. 10
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  3. 99
      StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs
  4. 12
      StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml
  5. 35
      StabilityMatrix.Avalonia/Views/OutputsPage.axaml
  6. 2
      StabilityMatrix.sln.DotSettings

6
CHANGELOG.md

@ -5,6 +5,12 @@ All notable changes to Stability Matrix will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning 2.0](https://semver.org/spec/v2.0.0.html).
## v2.10.0-dev.2
### Changed
- Revisited the way images are loaded on the outputs page, with improvements to loading speed & not freezing the UI while loading
### Fixed
- Fixed Outputs page not remembering where the user last was in the TreeView in certain circumstances
## v2.10.0-dev.1
### Changed
- Revamped the Packages page to enable running multiple packages at the same time

10
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -520,16 +520,16 @@ public static class DesignData
}
)
};
vm.Categories =
[
new PackageOutputCategory
vm.Categories = new ObservableCollectionExtended<PackageOutputCategory>
{
new()
{
Name = "Category 1",
Path = "path1",
SubDirectories = [new PackageOutputCategory { Name = "SubCategory 1", Path = "path3" }]
},
new PackageOutputCategory { Name = "Category 2", Path = "path2" }
];
new() { Name = "Category 2", Path = "path2" }
};
return vm;
}
}

99
StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

@ -1,11 +1,9 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -53,19 +51,21 @@ public partial class OutputsPageViewModel : PageViewModelBase
private readonly INotificationService notificationService;
private readonly INavigationService<MainWindowViewModel> navigationService;
private readonly ILogger<OutputsPageViewModel> logger;
private readonly List<CancellationTokenSource> cancellationTokenSources = [];
public override string Title => Resources.Label_OutputsPageTitle;
public override IconSource IconSource => new SymbolIconSource { Symbol = Symbol.Grid, IsFilled = true };
public SourceCache<LocalImageFile, string> OutputsCache { get; } = new(file => file.AbsolutePath);
private SourceCache<PackageOutputCategory, string> categoriesCache = new(category => category.Path);
public IObservableCollection<OutputImageViewModel> Outputs { get; set; } =
new ObservableCollectionExtended<OutputImageViewModel>();
public IEnumerable<SharedOutputType> OutputTypes { get; } = Enum.GetValues<SharedOutputType>();
[ObservableProperty]
private ObservableCollection<PackageOutputCategory> categories;
public IObservableCollection<PackageOutputCategory> Categories { get; set; } =
new ObservableCollectionExtended<PackageOutputCategory>();
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(CanShowOutputTypes))]
@ -93,6 +93,9 @@ public partial class OutputsPageViewModel : PageViewModelBase
[ObservableProperty]
private bool showFolders;
[ObservableProperty]
private bool isChangingCategory;
public bool CanShowOutputTypes => SelectedCategory?.Name?.Equals("Shared Output Folder") ?? false;
public string NumImagesSelected =>
@ -103,7 +106,6 @@ public partial class OutputsPageViewModel : PageViewModelBase
private string[] allowedExtensions = [".png", ".webp"];
private PackageOutputCategory? lastOutputCategory;
private CancellationTokenSource getOutputsTokenSource = new();
public OutputsPageViewModel(
ISettingsManager settingsManager,
@ -145,6 +147,8 @@ public partial class OutputsPageViewModel : PageViewModelBase
NumItemsSelected = Outputs.Count(o => o.IsSelected);
});
categoriesCache.Connect().DeferUntilLoaded().Bind(Categories).Subscribe();
settingsManager.RelayPropertyFor(
this,
vm => vm.ImageSize,
@ -161,11 +165,6 @@ public partial class OutputsPageViewModel : PageViewModelBase
}
protected override void OnInitialLoaded()
{
RefreshCategories();
}
public override void OnLoaded()
{
if (Design.IsDesignMode)
return;
@ -173,16 +172,18 @@ public partial class OutputsPageViewModel : PageViewModelBase
if (!settingsManager.IsLibraryDirSet)
return;
base.OnLoaded();
Directory.CreateDirectory(settingsManager.ImagesDirectory);
RefreshCategories();
SelectedCategory ??= Categories.First();
SelectedOutputType ??= SharedOutputType.All;
SearchQuery = string.Empty;
ImageSize = settingsManager.Settings.OutputsImageSize;
lastOutputCategory = SelectedCategory;
IsChangingCategory = true;
var path =
CanShowOutputTypes && SelectedOutputType != SharedOutputType.All
? Path.Combine(SelectedCategory.Path, SelectedOutputType.ToString())
@ -287,7 +288,12 @@ public partial class OutputsPageViewModel : PageViewModelBase
public void Refresh()
{
Dispatcher.UIThread.Post(RefreshCategories);
OnLoaded();
var path =
CanShowOutputTypes && SelectedOutputType != SharedOutputType.All
? Path.Combine(SelectedCategory.Path, SelectedOutputType.ToString())
: SelectedCategory.Path;
GetOutputs(path);
}
public async Task DeleteImage(OutputImageViewModel? item)
@ -515,7 +521,7 @@ public partial class OutputsPageViewModel : PageViewModelBase
}
}
OnLoaded();
Refresh();
IsConsolidating = false;
}
@ -544,35 +550,55 @@ public partial class OutputsPageViewModel : PageViewModelBase
if (lastOutputCategory?.Path.Equals(directory) is not true)
{
OutputsCache.Clear();
IsChangingCategory = true;
}
getOutputsTokenSource.Cancel();
getOutputsTokenSource = new CancellationTokenSource();
IsLoading = true;
Task.Run(
() =>
cancellationTokenSources.ForEach(cts => cts.Cancel());
Task.Run(() =>
{
var getOutputsTokenSource = new CancellationTokenSource();
cancellationTokenSources.Add(getOutputsTokenSource);
if (getOutputsTokenSource.IsCancellationRequested)
{
cancellationTokenSources.Remove(getOutputsTokenSource);
return;
}
var files = Directory
.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)
.Where(file => allowedExtensions.Contains(new FilePath(file).Extension))
.Select(file => LocalImageFile.FromPath(file))
.ToList();
if (getOutputsTokenSource.IsCancellationRequested)
{
var sw = Stopwatch.StartNew();
var files = Directory
.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)
.Where(file => allowedExtensions.Contains(new FilePath(file).Extension))
.Select(file => LocalImageFile.FromPath(file));
cancellationTokenSources.Remove(getOutputsTokenSource);
return;
}
Dispatcher.UIThread.Post(() =>
{
if (files.Count == 0 && OutputsCache.Count == 0)
{
IsLoading = false;
IsChangingCategory = false;
return;
}
OutputsCache.EditDiff(
files,
(oldItem, newItem) => oldItem.AbsolutePath == newItem.AbsolutePath
);
sw.Stop();
Console.WriteLine(
$"Finished with {OutputsCache.Count} files, took {sw.Elapsed.TotalMilliseconds}ms"
);
Dispatcher.UIThread.Post(() => IsLoading = false);
},
getOutputsTokenSource.Token
);
IsLoading = false;
IsChangingCategory = false;
});
cancellationTokenSources.Remove(getOutputsTokenSource);
});
}
private void RefreshCategories()
@ -619,10 +645,9 @@ public partial class OutputsPageViewModel : PageViewModelBase
}
);
Categories = new ObservableCollection<PackageOutputCategory>(packageCategories);
categoriesCache.EditDiff(packageCategories, (a, b) => a.Path == b.Path);
SelectedCategory =
Categories.FirstOrDefault(x => x.Name == previouslySelectedCategory?.Name) ?? Categories.First();
SelectedCategory = previouslySelectedCategory ?? Categories.First();
}
private ObservableCollection<PackageOutputCategory> GetSubfolders(string strPath)

12
StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml

@ -15,6 +15,7 @@
xmlns:converters="clr-namespace:StabilityMatrix.Avalonia.Converters"
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers"
xmlns:controls1="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
d:DataContext="{x:Static designData:DesignData.CivitAiBrowserViewModel}"
d:DesignHeight="700"
d:DesignWidth="800"
@ -100,7 +101,7 @@
CommandParameter="{Binding CivitModel}"
IsEnabled="{Binding !IsImporting}">
<Grid RowDefinitions="*, Auto">
<controls:BetterAdvancedImage Grid.Row="0"
<controls1:AsyncImage Grid.Row="0"
CornerRadius="8"
VerticalAlignment="Top"
HorizontalAlignment="Center"
@ -112,16 +113,15 @@
<!-- <controls:BetterAdvancedImage.RenderTransform> -->
<!-- <RotateTransform Angle="315"></RotateTransform> -->
<!-- </controls:BetterAdvancedImage.RenderTransform> -->
</controls:BetterAdvancedImage>
<controls:BetterAdvancedImage
</controls1:AsyncImage>
<controls1:AsyncImage
Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="8"
Width="330"
Height="400"
Source="{Binding CardImage}"
Stretch="UniformToFill"
StretchDirection="Both"/>
Stretch="UniformToFill"/>
<StackPanel
Grid.Row="0"
@ -180,7 +180,7 @@
Classes="transparent"
Padding="10,4">
<StackPanel Orientation="Horizontal" Spacing="6">
<controls:BetterAdvancedImage
<controls1:AsyncImage
Width="22"
Height="22"
Effect="{StaticResource ImageDropShadowEffect}"

35
StabilityMatrix.Avalonia/Views/OutputsPage.axaml

@ -2,15 +2,12 @@
x:Class="StabilityMatrix.Avalonia.Views.OutputsPage"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:avalonia="clr-namespace:Projektanker.Icons.Avalonia;assembly=Projektanker.Icons.Avalonia"
xmlns:avaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit"
xmlns:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:fluentAvalonia="clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mocks="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:models1="clr-namespace:StabilityMatrix.Avalonia.Models"
xmlns:outputsPage="clr-namespace:StabilityMatrix.Avalonia.ViewModels.OutputsPage"
xmlns:scroll="clr-namespace:StabilityMatrix.Avalonia.Controls.Scroll"
xmlns:selectableImageCard="clr-namespace:StabilityMatrix.Avalonia.Controls.SelectableImageCard"
@ -34,7 +31,11 @@
SelectedItem="{Binding SelectedCategory}">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding SubDirectories}">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Name}"
MaxWidth="250"
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis"
ToolTip.Tip="{Binding Name}"/>
</TreeDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
@ -81,13 +82,6 @@
</TextBox.InnerRightContent>
</TextBox>
<TextBlock
Grid.Row="0"
Grid.Column="3"
Margin="4"
IsVisible="{Binding !!NumItemsSelected}"
Text="{Binding NumImagesSelected}" />
<ui:CommandBar
Grid.Row="1"
Grid.Column="3"
@ -97,8 +91,13 @@
DefaultLabelPosition="Right">
<ui:CommandBar.PrimaryCommands>
<ui:CommandBarElementContainer>
<TextBlock Text="{Binding NumImagesSelected}"
IsVisible="{Binding NumItemsSelected}"
VerticalAlignment="Center"/>
</ui:CommandBarElementContainer>
<ui:CommandBarButton
IsEnabled="{Binding !!NumItemsSelected}"
IsEnabled="{Binding NumItemsSelected}"
Command="{Binding DeleteAllSelected}"
IconSource="Delete" />
<ui:CommandBarSeparator />
@ -110,7 +109,7 @@
<ui:CommandBarButton
Command="{Binding ClearSelection}"
IconSource="ClearSelection"
IsEnabled="{Binding !!NumItemsSelected}"
IsEnabled="{Binding NumItemsSelected}"
Label="{x:Static lang:Resources.Action_ClearSelection}" />
<ui:CommandBarSeparator />
<ui:CommandBarButton
@ -150,12 +149,22 @@
</ui:CommandBar.SecondaryCommands>
</ui:CommandBar>
</Grid>
<controls:ProgressRing Grid.Row="1"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Width="200"
Height="200"
IsIndeterminate="True"
IsVisible="{Binding IsChangingCategory}"/>
<scroll:BetterScrollViewer Grid.Column="1"
Grid.Row="1" PointerWheelChanged="ScrollViewer_MouseWheelChanged">
<ItemsRepeater
x:Name="ImageRepeater"
VerticalAlignment="Top"
HorizontalAlignment="Center"
ItemsSource="{Binding Outputs}">
<ItemsRepeater.Layout>
<UniformGridLayout MinColumnSpacing="8" MinRowSpacing="8" />

2
StabilityMatrix.sln.DotSettings

@ -7,6 +7,8 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LRU/@EntryIndexedValue">LRU</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=PC/@EntryIndexedValue">PC</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=4a98fdf6_002D7d98_002D4f5a_002Dafeb_002Dea44ad98c70c/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="FIELD" /&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/@KeyIndexDefined">True</s:Boolean>
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=1A64B45C2359DD448CF8A25D4A7469F9/@KeyIndexDefined">True</s:Boolean>
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/QuickList/=F0CA621CDF5AB24282D8CDC11C520997/Entry/=1A64B45C2359DD448CF8A25D4A7469F9/EntryName/@EntryValue">Class</s:String>

Loading…
Cancel
Save