Browse Source

changed how metadata is downloaded & added context menus & localization stuff

pull/629/head
JT 8 months ago
parent
commit
c217189ade
  1. 20
      StabilityMatrix.Avalonia/DesignData/DesignData.cs
  2. 63
      StabilityMatrix.Avalonia/Languages/Resources.Designer.cs
  3. 21
      StabilityMatrix.Avalonia/Languages/Resources.resx
  4. 22
      StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs
  5. 101
      StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
  6. 27
      StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
  7. 111
      StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml
  8. 17
      StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml
  9. 3
      StabilityMatrix.Core/Helper/EventManager.cs
  10. 12
      StabilityMatrix.Core/Models/PackageModification/DownloadOpenArtWorkflowStep.cs

20
StabilityMatrix.Avalonia/DesignData/DesignData.cs

@ -932,12 +932,20 @@ The gallery images are often inpainted, but you will get something very similar
{ {
new() new()
{ {
Name = "Test Workflow", Workflow = new()
Creator = "Test Creator", {
ThumbnailUrls = Name = "Test Workflow",
[ Creator = new OpenArtCreator { Name = "Test Creator" },
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512" Thumbnails =
] [
new OpenArtThumbnail
{
Url = new Uri(
"https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/dd9b038c-bd15-43ab-86ab-66e145ad7ff2/width=512"
)
}
]
}
} }
}; };

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

@ -1319,6 +1319,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Error retrieving workflows.
/// </summary>
public static string Label_ErrorRetrievingWorkflows {
get {
return ResourceManager.GetString("Label_ErrorRetrievingWorkflows", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Everything looks good!. /// Looks up a localized string similar to Everything looks good!.
/// </summary> /// </summary>
@ -1355,6 +1364,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Finished importing workflow and custom nodes.
/// </summary>
public static string Label_FinishedImportingWorkflow {
get {
return ResourceManager.GetString("Label_FinishedImportingWorkflow", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to First Page. /// Looks up a localized string similar to First Page.
/// </summary> /// </summary>
@ -2606,6 +2624,24 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Workflow Deleted.
/// </summary>
public static string Label_WorkflowDeleted {
get {
return ResourceManager.GetString("Label_WorkflowDeleted", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {0} deleted successfully.
/// </summary>
public static string Label_WorkflowDeletedSuccessfully {
get {
return ResourceManager.GetString("Label_WorkflowDeletedSuccessfully", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Workflow Description. /// Looks up a localized string similar to Workflow Description.
/// </summary> /// </summary>
@ -2615,6 +2651,24 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to The workflow and custom nodes have been imported..
/// </summary>
public static string Label_WorkflowImportComplete {
get {
return ResourceManager.GetString("Label_WorkflowImportComplete", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Workflow Imported.
/// </summary>
public static string Label_WorkflowImported {
get {
return ResourceManager.GetString("Label_WorkflowImported", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Workflows. /// Looks up a localized string similar to Workflows.
/// </summary> /// </summary>
@ -2705,6 +2759,15 @@ namespace StabilityMatrix.Avalonia.Languages {
} }
} }
/// <summary>
/// Looks up a localized string similar to Installed Workflows.
/// </summary>
public static string TabLabel_InstalledWorkflows {
get {
return ResourceManager.GetString("TabLabel_InstalledWorkflows", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Add a package to get started!. /// Looks up a localized string similar to Add a package to get started!.
/// </summary> /// </summary>

21
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -1047,4 +1047,25 @@
<data name="Label_StabilityMatrixAlreadyRunning" xml:space="preserve"> <data name="Label_StabilityMatrixAlreadyRunning" xml:space="preserve">
<value>Stability Matrix is already running</value> <value>Stability Matrix is already running</value>
</data> </data>
<data name="Label_WorkflowDeletedSuccessfully" xml:space="preserve">
<value>{0} deleted successfully</value>
</data>
<data name="Label_WorkflowDeleted" xml:space="preserve">
<value>Workflow Deleted</value>
</data>
<data name="Label_ErrorRetrievingWorkflows" xml:space="preserve">
<value>Error retrieving workflows</value>
</data>
<data name="TabLabel_InstalledWorkflows" xml:space="preserve">
<value>Installed Workflows</value>
</data>
<data name="Label_WorkflowImported" xml:space="preserve">
<value>Workflow Imported</value>
</data>
<data name="Label_FinishedImportingWorkflow" xml:space="preserve">
<value>Finished importing workflow and custom nodes</value>
</data>
<data name="Label_WorkflowImportComplete" xml:space="preserve">
<value>The workflow and custom nodes have been imported.</value>
</data>
</root> </root>

22
StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs

@ -2,27 +2,21 @@
using System.Linq; using System.Linq;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using StabilityMatrix.Avalonia; using StabilityMatrix.Core.Models.Api.OpenArt;
namespace StabilityMatrix.Core.Models.Api.OpenArt; namespace StabilityMatrix.Avalonia.Models;
public class OpenArtMetadata public class OpenArtMetadata
{ {
[JsonPropertyName("workflow_id")] [JsonPropertyName("sm_workflow_data")]
public string? Id { get; set; } public OpenArtSearchResult? Workflow { 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] [JsonIgnore]
public string? FirstThumbnail => ThumbnailUrls?.FirstOrDefault(); public string? FirstThumbnail => Workflow?.Thumbnails?.Select(x => x.Url).FirstOrDefault()?.ToString();
[JsonIgnore] [JsonIgnore]
public List<IStorageFile>? FilePath { get; set; } public List<IStorageFile>? FilePath { get; set; }
[JsonIgnore]
public bool HasMetadata => Workflow?.Creator != null;
} }

101
StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs

@ -1,28 +1,41 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks; using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Platform.Storage; using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using DynamicData; using DynamicData;
using DynamicData.Binding; using DynamicData.Binding;
using FluentAvalonia.UI.Controls;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Base;
using StabilityMatrix.Avalonia.Views; using StabilityMatrix.Avalonia.Views;
using StabilityMatrix.Core.Attributes; using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.Api.OpenArt; using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services; using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels; namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(InstalledWorkflowsPage))] [View(typeof(InstalledWorkflowsPage))]
[Singleton] [Singleton]
public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManager) : TabViewModelBase public partial class InstalledWorkflowsViewModel(
ISettingsManager settingsManager,
INotificationService notificationService
) : TabViewModelBase, IDisposable
{ {
public override string Header => "Installed Workflows"; public override string Header => Resources.TabLabel_InstalledWorkflows;
private readonly SourceCache<OpenArtMetadata, string> workflowsCache = new(x => x.Id); private readonly SourceCache<OpenArtMetadata, string> workflowsCache =
new(x => x.Workflow?.Id ?? Guid.NewGuid().ToString());
[ObservableProperty] [ObservableProperty]
private IObservableCollection<OpenArtMetadata> displayedWorkflows = private IObservableCollection<OpenArtMetadata> displayedWorkflows =
@ -38,6 +51,7 @@ public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManage
return; return;
await LoadInstalledWorkflowsAsync(); await LoadInstalledWorkflowsAsync();
EventManager.Instance.WorkflowInstalled += OnWorkflowInstalled;
} }
[RelayCommand] [RelayCommand]
@ -58,12 +72,15 @@ public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManage
var json = await File.ReadAllTextAsync(workflowPath); var json = await File.ReadAllTextAsync(workflowPath);
var metadata = JsonSerializer.Deserialize<OpenArtMetadata>(json); var metadata = JsonSerializer.Deserialize<OpenArtMetadata>(json);
if (metadata?.Id == null) if (metadata?.Workflow == null)
{ {
metadata = new OpenArtMetadata metadata = new OpenArtMetadata
{ {
Id = Guid.NewGuid().ToString(), Workflow = new OpenArtSearchResult
Name = Path.GetFileNameWithoutExtension(workflowPath) {
Id = Guid.NewGuid().ToString(),
Name = Path.GetFileNameWithoutExtension(workflowPath)
}
}; };
} }
@ -76,4 +93,76 @@ public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManage
} }
} }
} }
[RelayCommand]
private async Task OpenInExplorer(OpenArtMetadata metadata)
{
if (metadata.FilePath == null)
return;
var path = metadata.FilePath.FirstOrDefault()?.Path.ToString();
if (string.IsNullOrWhiteSpace(path))
return;
await ProcessRunner.OpenFileBrowser(path);
}
[RelayCommand]
private void OpenOnOpenArt(OpenArtMetadata metadata)
{
if (metadata.Workflow == null)
return;
ProcessRunner.OpenUrl($"https://openart.ai/workflows/{metadata.Workflow.Id}");
}
[RelayCommand]
private async Task DeleteAsync(OpenArtMetadata metadata)
{
var confirmationDialog = new BetterContentDialog
{
Title = Resources.Label_AreYouSure,
Content = Resources.Label_ActionCannotBeUndone,
PrimaryButtonText = Resources.Action_Delete,
SecondaryButtonText = Resources.Action_Cancel,
DefaultButton = ContentDialogButton.Primary,
IsSecondaryButtonEnabled = true,
};
var dialogResult = await confirmationDialog.ShowAsync();
if (dialogResult != ContentDialogResult.Primary)
return;
await using var delay = new MinimumDelay(200, 500);
var path = metadata?.FilePath?.FirstOrDefault()?.Path.ToString().Replace("file:///", "");
if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
{
await notificationService.TryAsync(
Task.Run(() => File.Delete(path)),
message: "Error deleting workflow"
);
var id = metadata?.Workflow?.Id;
if (!string.IsNullOrWhiteSpace(id))
{
workflowsCache.Remove(id);
}
}
notificationService.Show(
Resources.Label_WorkflowDeleted,
string.Format(Resources.Label_WorkflowDeletedSuccessfully, metadata?.Workflow?.Name)
);
}
private void OnWorkflowInstalled(object? sender, EventArgs e)
{
LoadInstalledWorkflowsAsync().SafeFireAndForget();
}
public void Dispose()
{
workflowsCache.Dispose();
EventManager.Instance.WorkflowInstalled -= OnWorkflowInstalled;
}
} }

27
StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs

@ -12,6 +12,7 @@ using DynamicData.Binding;
using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Controls;
using Refit; using Refit;
using StabilityMatrix.Avalonia.Controls; using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models; using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services; using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base; using StabilityMatrix.Avalonia.ViewModels.Base;
@ -182,10 +183,7 @@ public partial class OpenArtBrowserViewModel(
if (existingComfy == null || comfyPair == null) if (existingComfy == null || comfyPair == null)
{ {
notificationService.Show( notificationService.Show(Resources.Label_ComfyRequiredTitle, Resources.Label_ComfyRequiredDetail);
Resources.Label_ComfyRequiredTitle,
"ComfyUI is required to import workflows from OpenArt"
);
return; return;
} }
@ -210,18 +208,29 @@ public partial class OpenArtBrowserViewModel(
var runner = new PackageModificationRunner var runner = new PackageModificationRunner
{ {
ShowDialogOnStart = true, ShowDialogOnStart = true,
ModificationCompleteTitle = "Workflow Imported", ModificationCompleteTitle = Resources.Label_WorkflowImported,
ModificationCompleteMessage = "Finished importing workflow and custom nodes" ModificationCompleteMessage = Resources.Label_FinishedImportingWorkflow
}; };
EventManager.Instance.OnPackageInstallProgressAdded(runner); EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps); await runner.ExecuteSteps(steps);
notificationService.Show( notificationService.Show(
"Workflow Imported", Resources.Label_WorkflowImported,
"The workflow and custom nodes have been imported.", Resources.Label_WorkflowImportComplete,
NotificationType.Success NotificationType.Success
); );
EventManager.Instance.OnWorkflowInstalled();
}
[RelayCommand]
private void OpenOnOpenArt(OpenArtSearchResult? workflow)
{
if (workflow?.Id == null)
return;
ProcessRunner.OpenUrl($"https://openart.ai/workflows/{workflow.Id}");
} }
private async Task DoSearch(int page = 0) private async Task DoSearch(int page = 0)
@ -262,7 +271,7 @@ public partial class OpenArtBrowserViewModel(
} }
catch (ApiException e) catch (ApiException e)
{ {
notificationService.Show("Error retrieving workflows", e.Message); notificationService.Show(Resources.Label_ErrorRetrievingWorkflows, e.Message);
} }
finally finally
{ {

111
StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml

@ -10,6 +10,11 @@
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia" xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages" xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
xmlns:avalonia="https://github.com/projektanker/icons.avalonia" xmlns:avalonia="https://github.com/projektanker/icons.avalonia"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
xmlns:models="clr-namespace:StabilityMatrix.Avalonia.Models"
xmlns:helpers="clr-namespace:StabilityMatrix.Avalonia.Helpers"
xmlns:fluent="clr-namespace:FluentIcons.Avalonia.Fluent;assembly=FluentIcons.Avalonia.Fluent"
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
d:DataContext="{x:Static designData:DesignData.InstalledWorkflowsViewModel}" d:DataContext="{x:Static designData:DesignData.InstalledWorkflowsViewModel}"
x:DataType="viewModels:InstalledWorkflowsViewModel" x:DataType="viewModels:InstalledWorkflowsViewModel"
@ -22,7 +27,7 @@
</Transitions> </Transitions>
</Setter> </Setter>
<Style Selector="^ asyncImageLoader|AdvancedImage"> <Style Selector="^ labs|AsyncImage">
<Setter Property="Transitions"> <Setter Property="Transitions">
<Transitions> <Transitions>
<TransformOperationsTransition Property="RenderTransform" <TransformOperationsTransition Property="RenderTransform"
@ -60,6 +65,18 @@
</Style> </Style>
</Style> </Style>
</UserControl.Styles> </UserControl.Styles>
<UserControl.Resources>
<input:StandardUICommand
x:Key="OpenInExplorerCommand"
Command="{Binding OpenInExplorerCommand}" />
<input:StandardUICommand
x:Key="OpenOnOpenArtCommand"
Command="{Binding OpenOnOpenArtCommand}" />
<input:StandardUICommand
x:Key="DeleteCommand"
Command="{Binding DeleteCommand}" />
</UserControl.Resources>
<Grid RowDefinitions="Auto, *"> <Grid RowDefinitions="Auto, *">
<controls1:CommandBar Grid.Row="0" Grid.Column="0" <controls1:CommandBar Grid.Row="0" Grid.Column="0"
@ -73,16 +90,16 @@
VerticalAlignment="Center" VerticalAlignment="Center"
Label="{x:Static lang:Resources.Action_Refresh}" Label="{x:Static lang:Resources.Action_Refresh}"
Command="{Binding LoadInstalledWorkflowsCommand}" /> Command="{Binding LoadInstalledWorkflowsCommand}" />
<controls1:CommandBarSeparator /> <controls1:CommandBarSeparator />
<controls1:CommandBarElementContainer> <controls1:CommandBarElementContainer>
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
<avalonia:Icon FontSize="18" <avalonia:Icon FontSize="18"
Value="fa-solid fa-info" Value="fa-solid fa-info"
Margin="8,0"/> Margin="8,0" />
<TextBlock Text="Drag &amp; drop one of the cards below into ComfyUI to load the workflow" <TextBlock Text="Drag &amp; drop one of the cards below into ComfyUI to load the workflow"
VerticalAlignment="Center"/> VerticalAlignment="Center" />
</StackPanel> </StackPanel>
</controls1:CommandBarElementContainer> </controls1:CommandBarElementContainer>
</controls1:CommandBar.PrimaryCommands> </controls1:CommandBar.PrimaryCommands>
@ -96,7 +113,7 @@
<UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4" /> <UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4" />
</ItemsRepeater.Layout> </ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate> <ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="{x:Type openArt:OpenArtMetadata}"> <DataTemplate x:DataType="{x:Type models:OpenArtMetadata}">
<Border <Border
Name="HoverBorder" Name="HoverBorder"
Padding="0" Padding="0"
@ -113,6 +130,32 @@
VerticalDragThreshold="6" /> VerticalDragThreshold="6" />
</BehaviorCollection> </BehaviorCollection>
</Interaction.Behaviors> </Interaction.Behaviors>
<Border.ContextFlyout>
<MenuFlyout>
<MenuItem Header="{x:Static lang:Resources.Action_OpenOnOpenArt}"
IsVisible="{Binding HasMetadata}"
Command="{StaticResource OpenOnOpenArtCommand}"
CommandParameter="{Binding }">
<MenuItem.Icon>
<fluent:SymbolIcon Symbol="Open" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{x:Static lang:Resources.Action_OpenInExplorer}"
Command="{StaticResource OpenInExplorerCommand}"
CommandParameter="{Binding }">
<MenuItem.Icon>
<fluent:SymbolIcon Symbol="Folder" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{x:Static lang:Resources.Action_Delete}"
Command="{StaticResource DeleteCommand}"
CommandParameter="{Binding }">
<MenuItem.Icon>
<fluent:SymbolIcon Symbol="Delete" />
</MenuItem.Icon>
</MenuItem>
</MenuFlyout>
</Border.ContextFlyout>
<Button <Button
Name="ModelCard" Name="ModelCard"
Classes="transparent-full" Classes="transparent-full"
@ -121,7 +164,7 @@
VerticalContentAlignment="Top" VerticalContentAlignment="Top"
CornerRadius="8"> CornerRadius="8">
<Grid RowDefinitions="*, Auto"> <Grid RowDefinitions="*, Auto">
<controls:BetterAdvancedImage <labs:AsyncImage
Grid.Row="0" Grid.Row="0"
Grid.RowSpan="2" Grid.RowSpan="2"
CornerRadius="8" CornerRadius="8"
@ -129,17 +172,16 @@
Height="400" Height="400"
Source="{Binding FirstThumbnail}" Source="{Binding FirstThumbnail}"
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}" IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}"
Stretch="UniformToFill" Stretch="UniformToFill" />
StretchDirection="Both" />
<avalonia:Icon Grid.Row="0" <avalonia:Icon Grid.Row="0"
Grid.RowSpan="2" Grid.RowSpan="2"
CornerRadius="8" CornerRadius="8"
Width="330" Width="330"
Height="400" Height="400"
FontSize="100" FontSize="100"
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNull}, FallbackValue=False}" IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNull}, FallbackValue=False}"
Value="fa-regular fa-file-code"/> Value="fa-regular fa-file-code" />
<!-- Username pill card --> <!-- Username pill card -->
<Border <Border
@ -155,11 +197,34 @@
BlurRadius="12" BlurRadius="12"
Color="#FF000000" Color="#FF000000"
Opacity="0.9" /> Opacity="0.9" />
<DropShadowEffect
x:Key="ImageDropShadowEffect"
BlurRadius="12"
Color="#FF000000"
Opacity="0.2" />
</Border.Resources> </Border.Resources>
<TextBlock <Button
VerticalAlignment="Center" Command="{x:Static helpers:IOCommands.OpenUrlCommand}"
Effect="{StaticResource TextDropShadowEffect}" CommandParameter="{Binding Workflow.Creator.DevProfileUrl}"
Text="{Binding Creator}" /> CornerRadius="16"
Classes="transparent"
Padding="10,4">
<StackPanel Orientation="Horizontal" Spacing="6">
<controls:BetterAdvancedImage
Width="22"
Height="22"
Effect="{StaticResource ImageDropShadowEffect}"
CornerRadius="11"
RenderOptions.BitmapInterpolationMode="HighQuality"
IsVisible="{Binding Workflow.Creator.Avatar, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"
Source="{Binding Workflow.Creator.Avatar}" />
<TextBlock
VerticalAlignment="Center"
Foreground="{DynamicResource TextControlForeground}"
Effect="{StaticResource TextDropShadowEffect}"
Text="{Binding Workflow.Creator.Name}" />
</StackPanel>
</Button>
</Border> </Border>
<Border <Border
@ -174,8 +239,8 @@
Foreground="{DynamicResource TextControlForeground}" Foreground="{DynamicResource TextControlForeground}"
LetterSpacing="0.33" LetterSpacing="0.33"
TextWrapping="Wrap" TextWrapping="Wrap"
Text="{Binding Name}" Text="{Binding Workflow.Name}"
ToolTip.Tip="{Binding Name}" /> ToolTip.Tip="{Binding Workflow.Name}" />
</Border> </Border>
</Grid> </Grid>
</Button> </Button>

17
StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml

@ -12,6 +12,7 @@
xmlns:avalonia="https://github.com/projektanker/icons.avalonia" xmlns:avalonia="https://github.com/projektanker/icons.avalonia"
xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia" xmlns:input="clr-namespace:FluentAvalonia.UI.Input;assembly=FluentAvalonia"
xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels" xmlns:viewModels="clr-namespace:StabilityMatrix.Avalonia.ViewModels"
xmlns:labs="clr-namespace:Avalonia.Labs.Controls;assembly=Avalonia.Labs.Controls"
x:DataType="viewModels:OpenArtBrowserViewModel" x:DataType="viewModels:OpenArtBrowserViewModel"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="StabilityMatrix.Avalonia.Views.OpenArtBrowserPage"> x:Class="StabilityMatrix.Avalonia.Views.OpenArtBrowserPage">
@ -23,7 +24,7 @@
</Transitions> </Transitions>
</Setter> </Setter>
<Style Selector="^ asyncImageLoader|AdvancedImage"> <Style Selector="^ labs|AsyncImage">
<Setter Property="Transitions"> <Setter Property="Transitions">
<Transitions> <Transitions>
<TransformOperationsTransition Property="RenderTransform" <TransformOperationsTransition Property="RenderTransform"
@ -67,6 +68,10 @@
x:Key="OpenModelCommand" x:Key="OpenModelCommand"
Command="{Binding OpenModelCommand}" /> Command="{Binding OpenModelCommand}" />
<input:StandardUICommand
x:Key="OpenOnOpenArtCommand"
Command="{Binding OpenOnOpenArtCommand}" />
<input:StandardUICommand <input:StandardUICommand
x:Key="OpenWorkflowCommand" x:Key="OpenWorkflowCommand"
Command="{Binding OpenWorkflowCommand}" /> Command="{Binding OpenWorkflowCommand}" />
@ -82,7 +87,9 @@
CornerRadius="8"> CornerRadius="8">
<Border.ContextFlyout> <Border.ContextFlyout>
<MenuFlyout> <MenuFlyout>
<MenuItem Header="{x:Static lang:Resources.Action_OpenOnOpenArt}"> <MenuItem Header="{x:Static lang:Resources.Action_OpenOnOpenArt}"
Command="{StaticResource OpenOnOpenArtCommand}"
CommandParameter="{Binding }">
<MenuItem.Icon> <MenuItem.Icon>
<ui:SymbolIcon Symbol="Open" /> <ui:SymbolIcon Symbol="Open" />
</MenuItem.Icon> </MenuItem.Icon>
@ -99,15 +106,14 @@
Command="{StaticResource OpenWorkflowCommand}" Command="{StaticResource OpenWorkflowCommand}"
CommandParameter="{Binding }"> CommandParameter="{Binding }">
<Grid RowDefinitions="*, Auto"> <Grid RowDefinitions="*, Auto">
<controls:BetterAdvancedImage <labs: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 Thumbnails[0].Url}" Source="{Binding Thumbnails[0].Url}"
Stretch="UniformToFill" Stretch="UniformToFill" />
StretchDirection="Both" />
<!-- Username pill card --> <!-- Username pill card -->
<Border <Border
@ -146,6 +152,7 @@
Source="{Binding Creator.Avatar}" /> Source="{Binding Creator.Avatar}" />
<TextBlock <TextBlock
VerticalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource TextControlForeground}"
Effect="{StaticResource TextDropShadowEffect}" Effect="{StaticResource TextDropShadowEffect}"
Text="{Binding Creator.Name}" /> Text="{Binding Creator.Name}" />
</StackPanel> </StackPanel>

3
StabilityMatrix.Core/Helper/EventManager.cs

@ -46,6 +46,7 @@ public class EventManager
public event EventHandler<int>? NavigateAndFindCivitModelRequested; public event EventHandler<int>? NavigateAndFindCivitModelRequested;
public event EventHandler? DownloadsTeachingTipRequested; public event EventHandler? DownloadsTeachingTipRequested;
public event EventHandler? RecommendedModelsDialogClosed; public event EventHandler? RecommendedModelsDialogClosed;
public event EventHandler? WorkflowInstalled;
public void OnGlobalProgressChanged(int progress) => GlobalProgressChanged?.Invoke(this, progress); public void OnGlobalProgressChanged(int progress) => GlobalProgressChanged?.Invoke(this, progress);
@ -108,4 +109,6 @@ public class EventManager
public void OnPackageRelaunchRequested(InstalledPackage package) => public void OnPackageRelaunchRequested(InstalledPackage package) =>
PackageRelaunchRequested?.Invoke(this, package); PackageRelaunchRequested?.Invoke(this, package);
public void OnWorkflowInstalled() => WorkflowInstalled?.Invoke(this, EventArgs.Empty);
} }

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

@ -19,19 +19,13 @@ public class DownloadOpenArtWorkflowStep(
.DownloadWorkflowAsync(new OpenArtDownloadRequest { WorkflowId = workflow.Id }) .DownloadWorkflowAsync(new OpenArtDownloadRequest { WorkflowId = workflow.Id })
.ConfigureAwait(false); .ConfigureAwait(false);
var workflowJson = JsonSerializer.SerializeToNode(workflow);
Directory.CreateDirectory(settingsManager.WorkflowDirectory); Directory.CreateDirectory(settingsManager.WorkflowDirectory);
var filePath = Path.Combine(settingsManager.WorkflowDirectory, $"{workflowData.Filename}.json"); var filePath = Path.Combine(settingsManager.WorkflowDirectory, $"{workflowData.Filename}.json");
var jsonObject = JsonNode.Parse(workflowData.Payload) as JsonObject; var jsonObject = JsonNode.Parse(workflowData.Payload) as JsonObject;
jsonObject?.Add("workflow_id", workflow.Id); jsonObject?.Add("sm_workflow_data", workflowJson);
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); await File.WriteAllTextAsync(filePath, JsonSerializer.Serialize(jsonObject)).ConfigureAwait(false);

Loading…
Cancel
Save