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/), 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). 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 ## v2.10.0-dev.1
### Changed ### Changed
- Revamped the Packages page to enable running multiple packages at the same time - 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 = vm.Categories = new ObservableCollectionExtended<PackageOutputCategory>
[ {
new PackageOutputCategory new()
{ {
Name = "Category 1", Name = "Category 1",
Path = "path1", Path = "path1",
SubDirectories = [new PackageOutputCategory { Name = "SubCategory 1", Path = "path3" }] SubDirectories = [new PackageOutputCategory { Name = "SubCategory 1", Path = "path3" }]
}, },
new PackageOutputCategory { Name = "Category 2", Path = "path2" } new() { Name = "Category 2", Path = "path2" }
]; };
return vm; return vm;
} }
} }

99
StabilityMatrix.Avalonia/ViewModels/OutputsPageViewModel.cs

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

12
StabilityMatrix.Avalonia/Views/CivitAiBrowserPage.axaml

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

35
StabilityMatrix.Avalonia/Views/OutputsPage.axaml

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

Loading…
Cancel
Save