Browse Source

Added Installed Workflows tab

pull/629/head
JT 9 months ago
parent
commit
b5529f6ece
  1. 2
      StabilityMatrix.Avalonia/App.axaml.cs
  2. 208
      StabilityMatrix.Avalonia/Controls/BetterContextDragBehavior.cs
  3. 22
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  4. 18
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  5. 6
      StabilityMatrix.Avalonia/Languages/Resources.resx
  6. 28
      StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs
  7. 3
      StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj
  8. 3
      StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs
  9. 84
      StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
  10. 24
      StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
  11. 39
      StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs
  12. 189
      StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml
  13. 13
      StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml.cs
  14. 9
      StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml
  15. 12
      StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml
  16. 13
      StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml.cs
  17. 8
      StabilityMatrix.Core/Models/PackageModification/DownloadOpenArtWorkflowStep.cs

2
StabilityMatrix.Avalonia/App.axaml.cs

@ -334,7 +334,7 @@ public sealed class App : Application
provider.GetRequiredService<CheckpointsPageViewModel>(),
provider.GetRequiredService<CheckpointBrowserViewModel>(),
provider.GetRequiredService<OutputsPageViewModel>(),
provider.GetRequiredService<OpenArtBrowserViewModel>()
provider.GetRequiredService<WorkflowsPageViewModel>()
},
FooterPages = { provider.GetRequiredService<SettingsViewModel>() }
}

208
StabilityMatrix.Avalonia/Controls/BetterContextDragBehavior.cs

@ -0,0 +1,208 @@
using System;
using System.Threading.Tasks;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Xaml.Interactions.DragAndDrop;
using Avalonia.Xaml.Interactivity;
namespace StabilityMatrix.Avalonia.Controls;
public class BetterContextDragBehavior : Behavior<Control>
{
private Point _dragStartPoint;
private PointerEventArgs? _triggerEvent;
private bool _lock;
private bool _captured;
public static readonly StyledProperty<object?> ContextProperty = AvaloniaProperty.Register<
ContextDragBehavior,
object?
>(nameof(Context));
public static readonly StyledProperty<IDragHandler?> HandlerProperty = AvaloniaProperty.Register<
ContextDragBehavior,
IDragHandler?
>(nameof(Handler));
public static readonly StyledProperty<double> HorizontalDragThresholdProperty = AvaloniaProperty.Register<
ContextDragBehavior,
double
>(nameof(HorizontalDragThreshold), 3);
public static readonly StyledProperty<double> VerticalDragThresholdProperty = AvaloniaProperty.Register<
ContextDragBehavior,
double
>(nameof(VerticalDragThreshold), 3);
public static readonly StyledProperty<string> DataFormatProperty = AvaloniaProperty.Register<
BetterContextDragBehavior,
string
>("DataFormat");
public string DataFormat
{
get => GetValue(DataFormatProperty);
set => SetValue(DataFormatProperty, value);
}
public object? Context
{
get => GetValue(ContextProperty);
set => SetValue(ContextProperty, value);
}
public IDragHandler? Handler
{
get => GetValue(HandlerProperty);
set => SetValue(HandlerProperty, value);
}
public double HorizontalDragThreshold
{
get => GetValue(HorizontalDragThresholdProperty);
set => SetValue(HorizontalDragThresholdProperty, value);
}
public double VerticalDragThreshold
{
get => GetValue(VerticalDragThresholdProperty);
set => SetValue(VerticalDragThresholdProperty, value);
}
/// <inheritdoc />
protected override void OnAttachedToVisualTree()
{
AssociatedObject?.AddHandler(
InputElement.PointerPressedEvent,
AssociatedObject_PointerPressed,
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble
);
AssociatedObject?.AddHandler(
InputElement.PointerReleasedEvent,
AssociatedObject_PointerReleased,
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble
);
AssociatedObject?.AddHandler(
InputElement.PointerMovedEvent,
AssociatedObject_PointerMoved,
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble
);
AssociatedObject?.AddHandler(
InputElement.PointerCaptureLostEvent,
AssociatedObject_CaptureLost,
RoutingStrategies.Direct | RoutingStrategies.Tunnel | RoutingStrategies.Bubble
);
}
/// <inheritdoc />
protected override void OnDetachedFromVisualTree()
{
AssociatedObject?.RemoveHandler(InputElement.PointerPressedEvent, AssociatedObject_PointerPressed);
AssociatedObject?.RemoveHandler(InputElement.PointerReleasedEvent, AssociatedObject_PointerReleased);
AssociatedObject?.RemoveHandler(InputElement.PointerMovedEvent, AssociatedObject_PointerMoved);
AssociatedObject?.RemoveHandler(InputElement.PointerCaptureLostEvent, AssociatedObject_CaptureLost);
}
private async Task DoDragDrop(PointerEventArgs triggerEvent, object? value)
{
var data = new DataObject();
data.Set(DataFormat, value!);
var effect = DragDropEffects.None;
if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Alt))
{
effect |= DragDropEffects.Link;
}
else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Shift))
{
effect |= DragDropEffects.Move;
}
else if (triggerEvent.KeyModifiers.HasFlag(KeyModifiers.Control))
{
effect |= DragDropEffects.Copy;
}
else
{
effect |= DragDropEffects.Move;
}
await DragDrop.DoDragDrop(triggerEvent, data, effect);
}
private void Released()
{
_triggerEvent = null;
_lock = false;
}
private void AssociatedObject_PointerPressed(object? sender, PointerPressedEventArgs e)
{
var properties = e.GetCurrentPoint(AssociatedObject).Properties;
if (properties.IsLeftButtonPressed)
{
if (e.Source is Control control && AssociatedObject?.DataContext == control.DataContext)
{
_dragStartPoint = e.GetPosition(null);
_triggerEvent = e;
_lock = true;
_captured = true;
}
}
}
private void AssociatedObject_PointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (_captured)
{
if (e.InitialPressMouseButton == MouseButton.Left && _triggerEvent is { })
{
Released();
}
_captured = false;
}
}
private async void AssociatedObject_PointerMoved(object? sender, PointerEventArgs e)
{
var properties = e.GetCurrentPoint(AssociatedObject).Properties;
if (_captured && properties.IsLeftButtonPressed && _triggerEvent is { })
{
var point = e.GetPosition(null);
var diff = _dragStartPoint - point;
var horizontalDragThreshold = HorizontalDragThreshold;
var verticalDragThreshold = VerticalDragThreshold;
if (Math.Abs(diff.X) > horizontalDragThreshold || Math.Abs(diff.Y) > verticalDragThreshold)
{
if (_lock)
{
_lock = false;
}
else
{
return;
}
var context = Context ?? AssociatedObject?.DataContext;
Handler?.BeforeDragDrop(sender, _triggerEvent, context);
await DoDragDrop(_triggerEvent, context);
Handler?.AfterDragDrop(sender, _triggerEvent, context);
_triggerEvent = null;
}
}
}
private void AssociatedObject_CaptureLost(object? sender, PointerCaptureLostEventArgs e)
{
Released();
_captured = false;
}
}

22
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -899,6 +899,28 @@ The gallery images are often inpainted, but you will get something very similar
vm.IsBatchIndexEnabled = true;
});
public static InstalledWorkflowsViewModel InstalledWorkflowsViewModel
{
get
{
var vm = Services.GetRequiredService<InstalledWorkflowsViewModel>();
vm.DisplayedWorkflows = new ObservableCollectionExtended<OpenArtMetadata>
{
new()
{
Name = "Test Workflow",
Creator = "Test Creator",
ThumbnailUrls =
[
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512"
]
}
};
return vm;
}
}
public static IList<ICompletionData> SampleCompletionData =>
new List<ICompletionData>
{

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

@ -1607,6 +1607,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to Model Browser.
/// </summary>
public static string Label_ModelBrowser {
get {
return ResourceManager.GetString("Label_ModelBrowser", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Model Description.
/// </summary>
@ -1760,6 +1769,15 @@ namespace StabilityMatrix.Avalonia.Languages {
}
}
/// <summary>
/// Looks up a localized string similar to OpenArt Browser.
/// </summary>
public static string Label_OpenArtBrowser {
get {
return ResourceManager.GetString("Label_OpenArtBrowser", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Output Folder.
/// </summary>

6
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -993,4 +993,10 @@
<data name="Label_WorkflowDescription" xml:space="preserve">
<value>Workflow Description</value>
</data>
<data name="Label_OpenArtBrowser" xml:space="preserve">
<value>OpenArt Browser</value>
</data>
<data name="Label_ModelBrowser" xml:space="preserve">
<value>Model Browser</value>
</data>
</root>

28
StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs

@ -0,0 +1,28 @@
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using Avalonia.Platform.Storage;
using StabilityMatrix.Avalonia;
namespace StabilityMatrix.Core.Models.Api.OpenArt;
public class OpenArtMetadata
{
[JsonPropertyName("workflow_id")]
public string? Id { get; set; }
[JsonPropertyName("workflow_name")]
public string? Name { get; set; }
[JsonPropertyName("creator")]
public string? Creator { get; set; }
[JsonPropertyName("thumbnails")]
public IEnumerable<string>? ThumbnailUrls { get; set; }
[JsonIgnore]
public string? FirstThumbnail => ThumbnailUrls?.FirstOrDefault();
[JsonIgnore]
public List<IStorageFile>? FilePath { get; set; }
}

3
StabilityMatrix.Avalonia/StabilityMatrix.Avalonia.csproj

@ -197,6 +197,9 @@
<DependentUpon>NewInstallerDialog.axaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Update="Models\OpenArtMetadata.cs">
<Generator>MSBuild:GenerateCodeFromAttributes</Generator>
</Compile>
</ItemGroup>
<!-- set HUSKY to 0 to disable, or opt-in during CI by setting HUSKY to 1 -->

3
StabilityMatrix.Avalonia/ViewModels/CheckpointBrowserViewModel.cs

@ -6,6 +6,7 @@ using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.Core;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.ViewModels.CheckpointBrowser;
using StabilityMatrix.Avalonia.Views;
@ -20,7 +21,7 @@ namespace StabilityMatrix.Avalonia.ViewModels;
[Singleton]
public partial class CheckpointBrowserViewModel : PageViewModelBase
{
public override string Title => "Model Browser";
public override string Title => Resources.Label_ModelBrowser;
public override IconSource IconSource =>
new SymbolIconSource { Symbol = Symbol.BrainCircuit, IsFilled = true };

84
StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Platform.Storage;
using Avalonia.Xaml.Interactions.DragAndDrop;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
using DynamicData.Binding;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Services;
using Windows.Storage;
using IStorageFile = Avalonia.Platform.Storage.IStorageFile;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(InstalledWorkflowsPage))]
[Singleton]
public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManager) : TabViewModelBase
{
public override string Header => "Installed Workflows";
private readonly SourceCache<OpenArtMetadata, string> workflowsCache = new(x => x.Id);
[ObservableProperty]
private IObservableCollection<OpenArtMetadata> displayedWorkflows =
new ObservableCollectionExtended<OpenArtMetadata>();
protected override async Task OnInitialLoadedAsync()
{
await base.OnInitialLoadedAsync();
workflowsCache.Connect().DeferUntilLoaded().Bind(DisplayedWorkflows).Subscribe();
if (Design.IsDesignMode)
return;
await LoadInstalledWorkflowsAsync();
}
[RelayCommand]
private async Task LoadInstalledWorkflowsAsync()
{
workflowsCache.Clear();
foreach (
var workflowPath in Directory.EnumerateFiles(
settingsManager.WorkflowDirectory,
"*.json",
SearchOption.AllDirectories
)
)
{
try
{
var json = await File.ReadAllTextAsync(workflowPath);
var metadata = JsonSerializer.Deserialize<OpenArtMetadata>(json);
if (metadata?.Id == null)
{
metadata = new OpenArtMetadata
{
Id = Guid.NewGuid().ToString(),
Name = Path.GetFileNameWithoutExtension(workflowPath)
};
}
metadata.FilePath = [await App.StorageProvider.TryGetFileFromPathAsync(workflowPath)];
workflowsCache.AddOrUpdate(metadata);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
}

24
StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls.Notifications;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
@ -10,7 +10,6 @@ using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using Refit;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@ -20,12 +19,12 @@ using StabilityMatrix.Core.Api;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Helper.Factory;
using StabilityMatrix.Core.Models;
using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Models.PackageModification;
using StabilityMatrix.Core.Models.Packages.Extensions;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
using Resources = StabilityMatrix.Avalonia.Languages.Resources;
namespace StabilityMatrix.Avalonia.ViewModels;
@ -36,12 +35,11 @@ public partial class OpenArtBrowserViewModel(
INotificationService notificationService,
ISettingsManager settingsManager,
IPackageFactory packageFactory
) : PageViewModelBase, IInfinitelyScroll
) : TabViewModelBase, IInfinitelyScroll
{
private const int PageSize = 20;
public override string Title => Resources.Label_Workflows;
public override IconSource IconSource => new FASymbolIconSource { Symbol = "fa-solid fa-circle-nodes" };
public override string Header => Resources.Label_OpenArtBrowser;
private readonly SourceCache<OpenArtSearchResult, string> searchResultsCache = new(x => x.Id);
@ -82,14 +80,6 @@ public partial class OpenArtBrowserViewModel(
searchResultsCache.Connect().DeferUntilLoaded().Bind(SearchResults).Subscribe();
}
public override async Task OnLoadedAsync()
{
if (SearchResults.Any())
return;
await DoSearch();
}
[RelayCommand]
private async Task FirstPage()
{
@ -198,6 +188,12 @@ public partial class OpenArtBrowserViewModel(
EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps);
notificationService.Show(
"Workflow Imported",
"The workflow and custom nodes have been imported.",
NotificationType.Success
);
}
private async Task DoSearch(int page = 0)

39
StabilityMatrix.Avalonia/ViewModels/WorkflowsPageViewModel.cs

@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(WorkflowsPage))]
[Singleton]
public partial class WorkflowsPageViewModel : PageViewModelBase
{
public override string Title => Resources.Label_Workflows;
public override IconSource IconSource => new FASymbolIconSource { Symbol = "fa-solid fa-circle-nodes" };
public IReadOnlyList<TabItem> Pages { get; }
[ObservableProperty]
private TabItem? selectedPage;
/// <inheritdoc/>
public WorkflowsPageViewModel(
OpenArtBrowserViewModel openArtBrowserViewModel,
InstalledWorkflowsViewModel installedWorkflowsViewModel
)
{
Pages = new List<TabItem>(
new List<TabViewModelBase>([openArtBrowserViewModel, installedWorkflowsViewModel]).Select(
vm => new TabItem { Header = vm.Header, Content = vm }
)
);
SelectedPage = Pages.FirstOrDefault();
}
}

189
StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml

@ -0,0 +1,189 @@
<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:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:asyncImageLoader="clr-namespace:AsyncImageLoader;assembly=AsyncImageLoader.Avalonia"
xmlns:designData="clr-namespace:StabilityMatrix.Avalonia.DesignData"
xmlns:openArt="clr-namespace:StabilityMatrix.Core.Models.Api.OpenArt"
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:avalonia="https://github.com/projektanker/icons.avalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
d:DataContext="{x:Static designData:DesignData.InstalledWorkflowsViewModel}"
x:DataType="viewModels:InstalledWorkflowsViewModel"
x:Class="StabilityMatrix.Avalonia.Views.InstalledWorkflowsPage">
<UserControl.Styles>
<Style Selector="Border#HoverBorder">
<Setter Property="Transitions">
<Transitions>
<BoxShadowsTransition Property="BoxShadow" Duration="0:0:0.237" />
</Transitions>
</Setter>
<Style Selector="^ asyncImageLoader|AdvancedImage">
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform"
Duration="0:0:0.237">
<TransformOperationsTransition.Easing>
<QuadraticEaseInOut />
</TransformOperationsTransition.Easing>
</TransformOperationsTransition>
</Transitions>
</Setter>
</Style>
<Style Selector="^:pointerover">
<Setter Property="BoxShadow" Value="0 0 40 0 #60000000" />
<Setter Property="Cursor" Value="Hand" />
<Style Selector="^ asyncImageLoader|AdvancedImage">
<Setter Property="CornerRadius" Value="12" />
<Setter Property="RenderTransform" Value="scale(1.03, 1.03)" />
</Style>
<Style Selector="^ Border#ModelCardBottom">
<Setter Property="Background" Value="#CC000000" />
</Style>
</Style>
<Style Selector="^:not(:pointerover)">
<Setter Property="BoxShadow" Value="0 0 20 0 #60000000" />
<Setter Property="Cursor" Value="Arrow" />
<Style Selector="^ asyncImageLoader|AdvancedImage">
<Setter Property="CornerRadius" Value="8" />
<Setter Property="RenderTransform" Value="scale(1, 1)" />
</Style>
<Style Selector="^ Border#ModelCardBottom">
<Setter Property="Background" Value="#99000000" />
</Style>
</Style>
</Style>
</UserControl.Styles>
<Grid RowDefinitions="Auto, *">
<controls1:CommandBar Grid.Row="0" Grid.Column="0"
VerticalAlignment="Center"
HorizontalAlignment="Left"
VerticalContentAlignment="Center"
DefaultLabelPosition="Right">
<controls1:CommandBar.PrimaryCommands>
<controls1:CommandBarButton
IconSource="Refresh"
VerticalAlignment="Center"
Label="{x:Static lang:Resources.Action_Refresh}"
Command="{Binding LoadInstalledWorkflowsCommand}" />
<controls1:CommandBarSeparator />
<controls1:CommandBarElementContainer>
<StackPanel Orientation="Horizontal">
<controls1:SymbolIcon Symbol="Important"
Margin="8,0"
FontSize="20"/>
<TextBlock Text="Drag &amp; drop one of the cards below into ComfyUI to load the workflow"
VerticalAlignment="Center"
Margin="0,-2,0,0"/>
</StackPanel>
</controls1:CommandBarElementContainer>
</controls1:CommandBar.PrimaryCommands>
</controls1:CommandBar>
<ScrollViewer Grid.Column="0"
Grid.Row="1">
<ItemsRepeater ItemsSource="{Binding DisplayedWorkflows}">
<ItemsRepeater.Layout>
<!-- <UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4"/> -->
<UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="{x:Type openArt:OpenArtMetadata}">
<Border
Name="HoverBorder"
Padding="0"
BorderThickness="0"
Margin="8"
ClipToBounds="True"
CornerRadius="8">
<Interaction.Behaviors>
<BehaviorCollection>
<controls:BetterContextDragBehavior
Context="{Binding FilePath}"
DataFormat="Files"
HorizontalDragThreshold="6"
VerticalDragThreshold="6" />
</BehaviorCollection>
</Interaction.Behaviors>
<Button
Name="ModelCard"
Classes="transparent-full"
Padding="0"
BorderThickness="0"
VerticalContentAlignment="Top"
CornerRadius="8">
<Grid RowDefinitions="*, Auto">
<controls:BetterAdvancedImage
Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="8"
Width="330"
Height="400"
Source="{Binding FirstThumbnail}"
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}"
Stretch="UniformToFill"
StretchDirection="Both" />
<avalonia:Icon Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="8"
Width="330"
Height="400"
FontSize="100"
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNull}, FallbackValue=False}"
Value="fa-regular fa-file-code"/>
<!-- Username pill card -->
<Border
BoxShadow="inset 1.2 0 80 1.8 #66000000"
CornerRadius="16"
Margin="4"
Grid.Row="0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom">
<Border.Resources>
<DropShadowEffect
x:Key="TextDropShadowEffect"
BlurRadius="12"
Color="#FF000000"
Opacity="0.9" />
</Border.Resources>
<TextBlock
VerticalAlignment="Center"
Effect="{StaticResource TextDropShadowEffect}"
Text="{Binding Creator}" />
</Border>
<Border
Name="ModelCardBottom"
Grid.Row="1">
<TextBlock
Padding="16"
Margin="8,0,8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontWeight="SemiBold"
Foreground="{DynamicResource TextControlForeground}"
LetterSpacing="0.33"
TextWrapping="Wrap"
Text="{Binding Name}"
ToolTip.Tip="{Binding Name}" />
</Border>
</Grid>
</Button>
</Border>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</ScrollViewer>
</Grid>
</controls:UserControlBase>

13
StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml.cs

@ -0,0 +1,13 @@
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Views;
[Singleton]
public partial class InstalledWorkflowsPage : UserControlBase
{
public InstalledWorkflowsPage()
{
InitializeComponent();
}
}

9
StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml

@ -82,7 +82,7 @@
CornerRadius="8">
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="{x:Static lang:Resources.Action_OpenOnCivitAi}">
<MenuItem Header="{x:Static lang:Resources.Action_OpenOnOpenArt}">
<MenuItem.Icon>
<ui:SymbolIcon Symbol="Open" />
</MenuItem.Icon>
@ -243,13 +243,8 @@
</UserControl.Resources>
<Grid RowDefinitions="Auto, Auto, *, Auto">
<TextBlock Text="OpenArt Workflow Browser"
FontSize="24"
Margin="8,8" />
<Grid Grid.Row="1" ColumnDefinitions="*,Auto"
Margin="8,0">
Margin="8,8,8,0">
<TextBox
HorizontalAlignment="Stretch"
Text="{Binding SearchQuery, Mode=TwoWay}"

12
StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml

@ -0,0 +1,12 @@
<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:controls="clr-namespace:StabilityMatrix.Avalonia.Controls"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="viewModels:WorkflowsPageViewModel"
x:Class="StabilityMatrix.Avalonia.Views.WorkflowsPage">
<TabControl ItemsSource="{Binding Pages}"
SelectedItem="{Binding SelectedPage}"/>
</controls:UserControlBase>

13
StabilityMatrix.Avalonia/Views/WorkflowsPage.axaml.cs

@ -0,0 +1,13 @@
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Core.Attributes;
namespace StabilityMatrix.Avalonia.Views;
[Singleton]
public partial class WorkflowsPage : UserControlBase
{
public WorkflowsPage()
{
InitializeComponent();
}
}

8
StabilityMatrix.Core/Models/PackageModification/DownloadOpenArtWorkflowStep.cs

@ -24,6 +24,14 @@ public class DownloadOpenArtWorkflowStep(
var jsonObject = JsonNode.Parse(workflowData.Payload) as JsonObject;
jsonObject?.Add("workflow_id", workflow.Id);
jsonObject?.Add("workflow_name", workflow.Name);
jsonObject?.Add("creator", workflow.Creator.Username);
var thumbs = new JsonArray();
foreach (var thumb in workflow.Thumbnails)
{
thumbs.Add(thumb.Url);
}
jsonObject?.Add("thumbnails", thumbs);
await File.WriteAllTextAsync(filePath, JsonSerializer.Serialize(jsonObject)).ConfigureAwait(false);

Loading…
Cancel
Save