Browse Source

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

pull/629/head
JT 8 months ago
parent
commit
c217189ade
  1. 12
      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. 97
      StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs
  6. 27
      StabilityMatrix.Avalonia/ViewModels/OpenArtBrowserViewModel.cs
  7. 81
      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

12
StabilityMatrix.Avalonia/DesignData/DesignData.cs

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

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>
/// Looks up a localized string similar to Everything looks good!.
/// </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>
/// Looks up a localized string similar to First Page.
/// </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>
/// Looks up a localized string similar to Workflow Description.
/// </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>
/// Looks up a localized string similar to Workflows.
/// </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>
/// Looks up a localized string similar to Add a package to get started!.
/// </summary>

21
StabilityMatrix.Avalonia/Languages/Resources.resx

@ -1047,4 +1047,25 @@
<data name="Label_StabilityMatrixAlreadyRunning" xml:space="preserve">
<value>Stability Matrix is already running</value>
</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>

22
StabilityMatrix.Avalonia/Models/OpenArtMetadata.cs

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

97
StabilityMatrix.Avalonia/ViewModels/InstalledWorkflowsViewModel.cs

@ -1,28 +1,41 @@
using System;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using AsyncAwaitBestPractices;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using DynamicData;
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.Views;
using StabilityMatrix.Core.Attributes;
using StabilityMatrix.Core.Helper;
using StabilityMatrix.Core.Models.Api.OpenArt;
using StabilityMatrix.Core.Processes;
using StabilityMatrix.Core.Services;
namespace StabilityMatrix.Avalonia.ViewModels;
[View(typeof(InstalledWorkflowsPage))]
[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]
private IObservableCollection<OpenArtMetadata> displayedWorkflows =
@ -38,6 +51,7 @@ public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManage
return;
await LoadInstalledWorkflowsAsync();
EventManager.Instance.WorkflowInstalled += OnWorkflowInstalled;
}
[RelayCommand]
@ -58,12 +72,15 @@ public partial class InstalledWorkflowsViewModel(ISettingsManager settingsManage
var json = await File.ReadAllTextAsync(workflowPath);
var metadata = JsonSerializer.Deserialize<OpenArtMetadata>(json);
if (metadata?.Id == null)
if (metadata?.Workflow == null)
{
metadata = new OpenArtMetadata
{
Workflow = new OpenArtSearchResult
{
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 Refit;
using StabilityMatrix.Avalonia.Controls;
using StabilityMatrix.Avalonia.Languages;
using StabilityMatrix.Avalonia.Models;
using StabilityMatrix.Avalonia.Services;
using StabilityMatrix.Avalonia.ViewModels.Base;
@ -182,10 +183,7 @@ public partial class OpenArtBrowserViewModel(
if (existingComfy == null || comfyPair == null)
{
notificationService.Show(
Resources.Label_ComfyRequiredTitle,
"ComfyUI is required to import workflows from OpenArt"
);
notificationService.Show(Resources.Label_ComfyRequiredTitle, Resources.Label_ComfyRequiredDetail);
return;
}
@ -210,18 +208,29 @@ public partial class OpenArtBrowserViewModel(
var runner = new PackageModificationRunner
{
ShowDialogOnStart = true,
ModificationCompleteTitle = "Workflow Imported",
ModificationCompleteMessage = "Finished importing workflow and custom nodes"
ModificationCompleteTitle = Resources.Label_WorkflowImported,
ModificationCompleteMessage = Resources.Label_FinishedImportingWorkflow
};
EventManager.Instance.OnPackageInstallProgressAdded(runner);
await runner.ExecuteSteps(steps);
notificationService.Show(
"Workflow Imported",
"The workflow and custom nodes have been imported.",
Resources.Label_WorkflowImported,
Resources.Label_WorkflowImportComplete,
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)
@ -262,7 +271,7 @@ public partial class OpenArtBrowserViewModel(
}
catch (ApiException e)
{
notificationService.Show("Error retrieving workflows", e.Message);
notificationService.Show(Resources.Label_ErrorRetrievingWorkflows, e.Message);
}
finally
{

81
StabilityMatrix.Avalonia/Views/InstalledWorkflowsPage.axaml

@ -10,6 +10,11 @@
xmlns:controls1="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:lang="clr-namespace:StabilityMatrix.Avalonia.Languages"
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"
d:DataContext="{x:Static designData:DesignData.InstalledWorkflowsViewModel}"
x:DataType="viewModels:InstalledWorkflowsViewModel"
@ -22,7 +27,7 @@
</Transitions>
</Setter>
<Style Selector="^ asyncImageLoader|AdvancedImage">
<Style Selector="^ labs|AsyncImage">
<Setter Property="Transitions">
<Transitions>
<TransformOperationsTransition Property="RenderTransform"
@ -61,6 +66,18 @@
</Style>
</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, *">
<controls1:CommandBar Grid.Row="0" Grid.Column="0"
VerticalAlignment="Center"
@ -96,7 +113,7 @@
<UniformGridLayout MinColumnSpacing="4" MinRowSpacing="4" />
</ItemsRepeater.Layout>
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="{x:Type openArt:OpenArtMetadata}">
<DataTemplate x:DataType="{x:Type models:OpenArtMetadata}">
<Border
Name="HoverBorder"
Padding="0"
@ -113,6 +130,32 @@
VerticalDragThreshold="6" />
</BehaviorCollection>
</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
Name="ModelCard"
Classes="transparent-full"
@ -121,7 +164,7 @@
VerticalContentAlignment="Top"
CornerRadius="8">
<Grid RowDefinitions="*, Auto">
<controls:BetterAdvancedImage
<labs:AsyncImage
Grid.Row="0"
Grid.RowSpan="2"
CornerRadius="8"
@ -129,8 +172,7 @@
Height="400"
Source="{Binding FirstThumbnail}"
IsVisible="{Binding FirstThumbnail, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}"
Stretch="UniformToFill"
StretchDirection="Both" />
Stretch="UniformToFill" />
<avalonia:Icon Grid.Row="0"
Grid.RowSpan="2"
@ -155,11 +197,34 @@
BlurRadius="12"
Color="#FF000000"
Opacity="0.9" />
<DropShadowEffect
x:Key="ImageDropShadowEffect"
BlurRadius="12"
Color="#FF000000"
Opacity="0.2" />
</Border.Resources>
<Button
Command="{x:Static helpers:IOCommands.OpenUrlCommand}"
CommandParameter="{Binding Workflow.Creator.DevProfileUrl}"
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 Creator}" />
Text="{Binding Workflow.Creator.Name}" />
</StackPanel>
</Button>
</Border>
<Border
@ -174,8 +239,8 @@
Foreground="{DynamicResource TextControlForeground}"
LetterSpacing="0.33"
TextWrapping="Wrap"
Text="{Binding Name}"
ToolTip.Tip="{Binding Name}" />
Text="{Binding Workflow.Name}"
ToolTip.Tip="{Binding Workflow.Name}" />
</Border>
</Grid>
</Button>

17
StabilityMatrix.Avalonia/Views/OpenArtBrowserPage.axaml

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

3
StabilityMatrix.Core/Helper/EventManager.cs

@ -46,6 +46,7 @@ public class EventManager
public event EventHandler<int>? NavigateAndFindCivitModelRequested;
public event EventHandler? DownloadsTeachingTipRequested;
public event EventHandler? RecommendedModelsDialogClosed;
public event EventHandler? WorkflowInstalled;
public void OnGlobalProgressChanged(int progress) => GlobalProgressChanged?.Invoke(this, progress);
@ -108,4 +109,6 @@ public class EventManager
public void OnPackageRelaunchRequested(InstalledPackage 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 })
.ConfigureAwait(false);
var workflowJson = JsonSerializer.SerializeToNode(workflow);
Directory.CreateDirectory(settingsManager.WorkflowDirectory);
var filePath = Path.Combine(settingsManager.WorkflowDirectory, $"{workflowData.Filename}.json");
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);
jsonObject?.Add("sm_workflow_data", workflowJson);
await File.WriteAllTextAsync(filePath, JsonSerializer.Serialize(jsonObject)).ConfigureAwait(false);

Loading…
Cancel
Save