mirror of
https://github.com/BlossomiShymae/Needlework.Net.git
synced 2025-12-06 18:20:47 +01:00
WIP
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Xunit.Abstractions;
|
using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Needlework.Net.Core.Tests;
|
namespace Needlework.Net.Core.Tests;
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
using BlossomiShymae.GrrrLCU;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Core;
|
|
||||||
|
|
||||||
public static class LcuConnector
|
|
||||||
{
|
|
||||||
public static Func<ProcessInfo> GetProcessInfo { get; } = Connector.GetProcessInfo;
|
|
||||||
public static Func<int, string, Uri> GetLeagueClientUri { get; } = Connector.GetLeagueClientUri;
|
|
||||||
public static Func<HttpMethod, string, CancellationToken, Task<HttpResponseMessage>> SendAsync { get; } = Connector.SendAsync;
|
|
||||||
}
|
|
||||||
@@ -6,44 +6,67 @@ public class LcuSchemaHandler
|
|||||||
{
|
{
|
||||||
internal OpenApiDocument OpenApiDocument { get; }
|
internal OpenApiDocument OpenApiDocument { get; }
|
||||||
|
|
||||||
public SortedDictionary<string, OpenApiPathItem> Plugins { get; } = [];
|
public SortedDictionary<string, List<PathOperation>> Plugins { get; }
|
||||||
|
|
||||||
public OpenApiInfo Info => OpenApiDocument.Info;
|
public OpenApiInfo Info => OpenApiDocument.Info;
|
||||||
|
|
||||||
|
public List<string> Paths => [.. OpenApiDocument.Paths.Keys];
|
||||||
|
|
||||||
public LcuSchemaHandler(OpenApiDocument openApiDocument)
|
public LcuSchemaHandler(OpenApiDocument openApiDocument)
|
||||||
{
|
{
|
||||||
OpenApiDocument = openApiDocument;
|
OpenApiDocument = openApiDocument;
|
||||||
|
var plugins = new SortedDictionary<string, List<PathOperation>>();
|
||||||
|
|
||||||
// Group paths by plugins
|
foreach ((var path, var pathItem) in openApiDocument.Paths)
|
||||||
foreach (var tag in OpenApiDocument.Tags)
|
|
||||||
{
|
{
|
||||||
foreach (var path in OpenApiDocument.Paths)
|
foreach ((var method, var operation) in pathItem.Operations)
|
||||||
{
|
{
|
||||||
var containsTag = false;
|
var operations = new List<PathOperation>();
|
||||||
var sentinelTag = string.Empty;
|
var pluginsKey = "_unknown";
|
||||||
|
|
||||||
foreach (var operation in path.Value.Operations)
|
// Process and group endpoints into the following formats:
|
||||||
|
// "_unknown" - group that should not be possible
|
||||||
|
// "default" - no tags
|
||||||
|
// "builtin" - 'builtin' not associated with an endpoint
|
||||||
|
// "lol-summoner" etc. - 'plugin' associated with an endpoint
|
||||||
|
// "performance", "tracing", etc.
|
||||||
|
if (operation.Tags.Count == 0)
|
||||||
{
|
{
|
||||||
foreach (var operationTag in operation.Value.Tags)
|
pluginsKey = "default";
|
||||||
|
if (plugins.TryGetValue(pluginsKey, out var p))
|
||||||
|
p.Add(new(method.ToString(), path, operation));
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var lhs = tag.Name.Replace("Plugin ", string.Empty);
|
operations.Add(new(method.ToString(), path, operation));
|
||||||
var rhs = operationTag.Name.Replace("Plugin ", string.Empty);
|
plugins[pluginsKey] = operations;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var tag in operation.Tags)
|
||||||
|
{
|
||||||
|
var lowercaseTag = tag.Name.ToLower();
|
||||||
|
if (lowercaseTag == "plugins")
|
||||||
|
continue;
|
||||||
|
else if (lowercaseTag.Contains("plugin "))
|
||||||
|
pluginsKey = lowercaseTag.Replace("plugin ", "");
|
||||||
|
else
|
||||||
|
pluginsKey = lowercaseTag;
|
||||||
|
|
||||||
if (lhs.Equals(rhs, StringComparison.OrdinalIgnoreCase))
|
if (plugins.TryGetValue(pluginsKey, out var p))
|
||||||
|
p.Add(new(method.ToString(), path, operation));
|
||||||
|
else
|
||||||
{
|
{
|
||||||
containsTag = true;
|
operations.Add(new(method.ToString(), path, operation));
|
||||||
sentinelTag = lhs.ToLower();
|
plugins[pluginsKey] = operations;
|
||||||
break; // Break early since all operations in a path share the same tags
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (containsTag)
|
Plugins = plugins;
|
||||||
break; // Ditto
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (containsTag)
|
public record PathOperation(string Method, string Path, OpenApiOperation Operation);
|
||||||
Plugins[sentinelTag] = path.Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BlossomiShymae.GrrrLCU" Version="0.4.0" />
|
|
||||||
<PackageReference Include="Microsoft.OpenApi" Version="1.6.16" />
|
<PackageReference Include="Microsoft.OpenApi" Version="1.6.16" />
|
||||||
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.16" />
|
<PackageReference Include="Microsoft.OpenApi.Readers" Version="1.6.16" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
x:Class="Needlework.Net.Desktop.App"
|
x:Class="Needlework.Net.Desktop.App"
|
||||||
RequestedThemeVariant="Dark"
|
RequestedThemeVariant="Dark"
|
||||||
xmlns:local="using:Needlework.Net.Desktop"
|
xmlns:local="using:Needlework.Net.Desktop"
|
||||||
|
xmlns:converters="using:Needlework.Net.Desktop.Converters"
|
||||||
xmlns:sukiUi="clr-namespace:SukiUI;assembly=SukiUI"
|
xmlns:sukiUi="clr-namespace:SukiUI;assembly=SukiUI"
|
||||||
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
|
xmlns:materialIcons="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia">
|
||||||
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
|
||||||
@@ -15,5 +16,11 @@
|
|||||||
<sukiUi:SukiTheme ThemeColor="Blue" />
|
<sukiUi:SukiTheme ThemeColor="Blue" />
|
||||||
<materialIcons:MaterialIconStyles />
|
<materialIcons:MaterialIconStyles />
|
||||||
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
<StyleInclude Source="avares://AvaloniaEdit/Themes/Fluent/AvaloniaEdit.xaml" />
|
||||||
|
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml"/>
|
||||||
</Application.Styles>
|
</Application.Styles>
|
||||||
|
|
||||||
|
<Application.Resources>
|
||||||
|
<converters:EnumerableBoolConverter x:Key="EnumerableBoolConverter"/>
|
||||||
|
<converters:NullBoolConverter x:Key="NullBoolConverter"/>
|
||||||
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -15,9 +16,14 @@ public partial class App(IServiceProvider serviceProvider) : Application
|
|||||||
|
|
||||||
public static JsonSerializerOptions JsonSerializerOptions { get; } = new()
|
public static JsonSerializerOptions JsonSerializerOptions { get; } = new()
|
||||||
{
|
{
|
||||||
WriteIndented = true
|
WriteIndented = true,
|
||||||
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static readonly int MaxCharacters = 10_000;
|
||||||
|
|
||||||
|
public static Window? MainWindow;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(this);
|
AvaloniaXamlLoader.Load(this);
|
||||||
@@ -31,8 +37,11 @@ public partial class App(IServiceProvider serviceProvider) : Application
|
|||||||
{
|
{
|
||||||
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
DataContext = _serviceProvider.GetRequiredService<MainWindowViewModel>()
|
||||||
};
|
};
|
||||||
|
MainWindow = desktop.MainWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
base.OnFrameworkInitializationCompleted();
|
base.OnFrameworkInitializationCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BIN
Needlework.Net.Desktop/Assets/app.ico
Normal file
BIN
Needlework.Net.Desktop/Assets/app.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
BIN
Needlework.Net.Desktop/Assets/app.png
Normal file
BIN
Needlework.Net.Desktop/Assets/app.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.1 KiB |
22
Needlework.Net.Desktop/Converters/EnumerableBoolConverter.cs
Normal file
22
Needlework.Net.Desktop/Converters/EnumerableBoolConverter.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Converters
|
||||||
|
{
|
||||||
|
public class EnumerableBoolConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
if (value is IEnumerable<object> values) return values.Any();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Needlework.Net.Desktop/Converters/NullBoolConverter.cs
Normal file
19
Needlework.Net.Desktop/Converters/NullBoolConverter.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Avalonia.Data.Converters;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Converters
|
||||||
|
{
|
||||||
|
public class NullBoolConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
Needlework.Net.Desktop/Extensions/TextEditorExtensions.cs
Normal file
29
Needlework.Net.Desktop/Extensions/TextEditorExtensions.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using Avalonia.Media;
|
||||||
|
using AvaloniaEdit;
|
||||||
|
using AvaloniaEdit.Highlighting;
|
||||||
|
using AvaloniaEdit.Indentation.CSharp;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Extensions
|
||||||
|
{
|
||||||
|
public static class TextEditorExtensions
|
||||||
|
{
|
||||||
|
public static void ApplyJsonEditorSettings(this TextEditor textEditor)
|
||||||
|
{
|
||||||
|
textEditor.TextArea.IndentationStrategy = new CSharpIndentationStrategy(textEditor.Options);
|
||||||
|
textEditor.TextArea.RightClickMovesCaret = true;
|
||||||
|
textEditor.TextArea.Options.EnableHyperlinks = false;
|
||||||
|
textEditor.TextArea.Options.EnableEmailHyperlinks = false;
|
||||||
|
textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("Json");
|
||||||
|
|
||||||
|
var purple = Color.FromRgb(189, 147, 249);
|
||||||
|
var yellow = Color.FromRgb(241, 250, 140);
|
||||||
|
var cyan = Color.FromRgb(139, 233, 253);
|
||||||
|
textEditor.SyntaxHighlighting.GetNamedColor("Bool").Foreground = new SimpleHighlightingBrush(purple);
|
||||||
|
textEditor.SyntaxHighlighting.GetNamedColor("Number").Foreground = new SimpleHighlightingBrush(purple);
|
||||||
|
textEditor.SyntaxHighlighting.GetNamedColor("String").Foreground = new SimpleHighlightingBrush(yellow);
|
||||||
|
textEditor.SyntaxHighlighting.GetNamedColor("Null").Foreground = new SimpleHighlightingBrush(purple);
|
||||||
|
textEditor.SyntaxHighlighting.GetNamedColor("FieldName").Foreground = new SimpleHighlightingBrush(cyan);
|
||||||
|
textEditor.SyntaxHighlighting.GetNamedColor("Punctuation").Foreground = new SimpleHighlightingBrush(yellow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
Needlework.Net.Desktop/Messages/ContentRequestMessage.cs
Normal file
8
Needlework.Net.Desktop/Messages/ContentRequestMessage.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Messages
|
||||||
|
{
|
||||||
|
public class ContentRequestMessage : RequestMessage<string>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Needlework.Net.Desktop/Messages/DataReadyMessage.cs
Normal file
9
Needlework.Net.Desktop/Messages/DataReadyMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
using Needlework.Net.Core;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Messages
|
||||||
|
{
|
||||||
|
public class DataReadyMessage(LcuSchemaHandler handler) : ValueChangedMessage<LcuSchemaHandler>(handler)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
9
Needlework.Net.Desktop/Messages/DataRequestMessage.cs
Normal file
9
Needlework.Net.Desktop/Messages/DataRequestMessage.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
using Needlework.Net.Core;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Messages
|
||||||
|
{
|
||||||
|
public class DataRequestMessage : RequestMessage<LcuSchemaHandler>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Needlework.Net.Desktop/Messages/EditorUpdateMessage.cs
Normal file
20
Needlework.Net.Desktop/Messages/EditorUpdateMessage.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Messages
|
||||||
|
{
|
||||||
|
public class EditorUpdateMessage(EditorUpdate editorUpdate) : ValueChangedMessage<EditorUpdate>(editorUpdate)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EditorUpdate
|
||||||
|
{
|
||||||
|
public string Text { get; }
|
||||||
|
public string Key { get; }
|
||||||
|
|
||||||
|
public EditorUpdate(string text, string key)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Messages
|
||||||
|
{
|
||||||
|
public class HostDocumentRequestMessage : RequestMessage<OpenApiDocument>
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Messages
|
||||||
|
{
|
||||||
|
public class OopsiesWindowCanceledMessage(object? data) : ValueChangedMessage<object?>(data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Messages
|
||||||
|
{
|
||||||
|
public class OopsiesWindowRequestedMessage(string text) : ValueChangedMessage<string>(text)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Messages
|
||||||
|
{
|
||||||
|
public class ResponseUpdatedMessage(string data) : ValueChangedMessage<string>(data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
<BuiltInComInteropSupport>true</BuiltInComInteropSupport>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Label="Avalonia">
|
<PropertyGroup Label="Avalonia">
|
||||||
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
<AvaloniaXamlIlDebuggerLaunch>False</AvaloniaXamlIlDebuggerLaunch>
|
||||||
<SupportedOSPlatformVersion>10.0.17763.0</SupportedOSPlatformVersion>
|
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||||
<AssemblyVersion>0.1.0.0</AssemblyVersion>
|
<AssemblyVersion>0.1.0.0</AssemblyVersion>
|
||||||
<FileVersion>0.1.0.0</FileVersion>
|
<FileVersion>0.1.0.0</FileVersion>
|
||||||
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
<AvaloniaXamlVerboseExceptions>False</AvaloniaXamlVerboseExceptions>
|
||||||
@@ -18,12 +18,15 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="11.1.0-beta2" />
|
<PackageReference Include="Avalonia" Version="11.1.0-beta2" />
|
||||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
|
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
|
||||||
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.1.0-beta2" />
|
||||||
|
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.0-beta2" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.1.0-beta2" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.1.0-beta2" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.0-beta2" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.1.0-beta2" />
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.0-beta2" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.1.0-beta2" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.0-beta2" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.0-beta2" />
|
||||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
|
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
|
||||||
|
<PackageReference Include="BlossomiShymae.GrrrLCU" Version="0.8.0" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||||
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
|
<PackageReference Include="Material.Icons.Avalonia" Version="2.1.10" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||||
@@ -45,4 +48,17 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<UpToDateCheckInput Remove="Views\AboutView.axaml" />
|
<UpToDateCheckInput Remove="Views\AboutView.axaml" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Views\EndpointView.axaml.cs">
|
||||||
|
<DependentUpon>EndpointView.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Views\OopsiesWindow.axaml.cs">
|
||||||
|
<DependentUpon>OopsiesWindow.axaml</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Utilities\" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class Program
|
|||||||
var builder = new ServiceCollection();
|
var builder = new ServiceCollection();
|
||||||
|
|
||||||
builder.AddSingleton<MainWindowViewModel>();
|
builder.AddSingleton<MainWindowViewModel>();
|
||||||
builder.AddSingleton<DialogService>();
|
builder.AddSingleton<WindowService>();
|
||||||
// Dynamically add ViewModels
|
// Dynamically add ViewModels
|
||||||
var types = AppDomain.CurrentDomain.GetAssemblies()
|
var types = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
.SelectMany(s => s.GetTypes())
|
.SelectMany(s => s.GetTypes())
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
using Needlework.Net.Desktop.ViewModels;
|
|
||||||
using Needlework.Net.Desktop.Views;
|
|
||||||
using SukiUI.Controls;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop.Services
|
|
||||||
{
|
|
||||||
public class DialogService
|
|
||||||
{
|
|
||||||
public IServiceProvider ServiceProvider { get; }
|
|
||||||
|
|
||||||
public Dictionary<string, SukiWindow> Dialogs { get; } = [];
|
|
||||||
|
|
||||||
public DialogService(IServiceProvider serviceProvider)
|
|
||||||
{
|
|
||||||
ServiceProvider = serviceProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ShowEndpoint(string endpoint)
|
|
||||||
{
|
|
||||||
if (!Dialogs.TryGetValue(endpoint, out var _))
|
|
||||||
{
|
|
||||||
var dialog = new EndpointView();
|
|
||||||
dialog.DataContext = new EndpointViewModel(endpoint);
|
|
||||||
dialog.Show();
|
|
||||||
dialog.Closed += OnDialogClosed;
|
|
||||||
Dialogs[endpoint] = dialog;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnDialogClosed(object? sender, EventArgs e)
|
|
||||||
{
|
|
||||||
if (sender == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var dialog = (SukiWindow)sender;
|
|
||||||
if (dialog.DataContext is EndpointViewModel vm)
|
|
||||||
{
|
|
||||||
Dialogs.Remove(vm.Endpoint);
|
|
||||||
dialog.DataContext = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
dialog.Closed -= OnDialogClosed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
using Avalonia.Media;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using Needlework.Net.Core;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop.Services
|
|
||||||
{
|
|
||||||
public partial class LcuService : ObservableObject
|
|
||||||
{
|
|
||||||
public HttpClient HttpClient { get; }
|
|
||||||
|
|
||||||
public LcuSchemaHandler LcuSchemaHandler { get; }
|
|
||||||
|
|
||||||
[ObservableProperty] private string _statusText = "Offline";
|
|
||||||
[ObservableProperty] private IBrush _statusColor = new SolidColorBrush(Colors.Red.ToUInt32());
|
|
||||||
[ObservableProperty] private string _statusAddress = "N/A";
|
|
||||||
|
|
||||||
public LcuService(HttpClient httpClient)
|
|
||||||
{
|
|
||||||
HttpClient = httpClient;
|
|
||||||
|
|
||||||
Task.Run(ProcessBackground);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessBackground()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
53
Needlework.Net.Desktop/Services/WindowService.cs
Normal file
53
Needlework.Net.Desktop/Services/WindowService.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using Needlework.Net.Desktop.ViewModels;
|
||||||
|
using Needlework.Net.Desktop.Views;
|
||||||
|
using SukiUI.Controls;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Services
|
||||||
|
{
|
||||||
|
public class WindowService : IRecipient<OopsiesWindowCanceledMessage>
|
||||||
|
{
|
||||||
|
public IServiceProvider ServiceProvider { get; }
|
||||||
|
|
||||||
|
public Dictionary<string, SukiWindow> EndpointWindows { get; } = []; // Workaround memory leak by storing and reusing windows.
|
||||||
|
// Figure out why creating and closing windows leaks memory.
|
||||||
|
|
||||||
|
public OopsiesWindow? OopsiesWindow { get; set; }
|
||||||
|
|
||||||
|
public WindowService(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
ServiceProvider = serviceProvider;
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Register<OopsiesWindowCanceledMessage>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowOopsiesWindow(string text)
|
||||||
|
{
|
||||||
|
if (OopsiesWindow != null) OopsiesWindow!.Close();
|
||||||
|
|
||||||
|
var window = new OopsiesWindow();
|
||||||
|
window.DataContext = new OopsiesWindowViewModel(text);
|
||||||
|
window.Show(App.MainWindow!);
|
||||||
|
window.Closed += OnOopsiesWindowClosed;
|
||||||
|
OopsiesWindow = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnOopsiesWindowClosed(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender == null) return;
|
||||||
|
|
||||||
|
var window = (OopsiesWindow)sender;
|
||||||
|
window.DataContext = null;
|
||||||
|
window.Closed -= OnOopsiesWindowClosed;
|
||||||
|
OopsiesWindow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(OopsiesWindowCanceledMessage message)
|
||||||
|
{
|
||||||
|
if (OopsiesWindow is OopsiesWindow window) window.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop
|
|
||||||
{
|
|
||||||
public class TextUpdatedEventArgs(string text) : EventArgs
|
|
||||||
{
|
|
||||||
public string Text { get; } = text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
using Needlework.Net.Desktop.ViewModels;
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop
|
namespace Needlework.Net.Desktop
|
||||||
{
|
{
|
||||||
@@ -9,27 +9,20 @@ namespace Needlework.Net.Desktop
|
|||||||
{
|
{
|
||||||
public Control? Build(object? param)
|
public Control? Build(object? param)
|
||||||
{
|
{
|
||||||
if (param is null)
|
if (param is null) return new TextBlock { Text = "data was null" };
|
||||||
return new TextBlock { Text = "data was null" };
|
|
||||||
|
|
||||||
var name = param.GetType().FullName!.Replace("ViewModels", "Views")
|
var name = param.GetType().FullName!
|
||||||
|
.Replace("ViewModels", "Views")
|
||||||
.Replace("ViewModel", "View");
|
.Replace("ViewModel", "View");
|
||||||
var type = Type.GetType(name);
|
var type = Type.GetType(name);
|
||||||
|
|
||||||
if (type != null)
|
if (type != null) return (Control)Activator.CreateInstance(type)!;
|
||||||
{
|
else return new TextBlock { Text = "Not Found: " + name };
|
||||||
return (Control)Activator.CreateInstance(type)!;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new TextBlock { Text = "Not Found: " + name };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Match(object? data)
|
public bool Match(object? data)
|
||||||
{
|
{
|
||||||
if (data is PageBase) return true;
|
return data is INotifyPropertyChanged;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,17 +2,25 @@
|
|||||||
using BlossomiShymae.GrrrLCU;
|
using BlossomiShymae.GrrrLCU;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using Needlework.Net.Desktop.Services;
|
||||||
|
using Needlework.Net.Desktop.Views;
|
||||||
using SukiUI.Controls;
|
using SukiUI.Controls;
|
||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop.ViewModels
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
{
|
{
|
||||||
public partial class ConsoleViewModel : PageBase
|
public partial class ConsoleViewModel : PageBase, IRecipient<OopsiesWindowRequestedMessage>, IRecipient<DataReadyMessage>
|
||||||
{
|
{
|
||||||
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
|
public IAvaloniaReadOnlyList<string> RequestMethods { get; } = new AvaloniaList<string>(["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS", "TRACE"]);
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
|
[ObservableProperty] private bool _isRequestBusy = false;
|
||||||
|
[ObservableProperty] private IAvaloniaReadOnlyList<string> _requestPaths = new AvaloniaList<string>();
|
||||||
[ObservableProperty] private string? _requestMethodSelected = "GET";
|
[ObservableProperty] private string? _requestMethodSelected = "GET";
|
||||||
[ObservableProperty] private string? _requestPath = null;
|
[ObservableProperty] private string? _requestPath = null;
|
||||||
[ObservableProperty] private string? _requestBody = null;
|
[ObservableProperty] private string? _requestBody = null;
|
||||||
@@ -20,10 +28,14 @@ namespace Needlework.Net.Desktop.ViewModels
|
|||||||
[ObservableProperty] private string? _responseStatus = null;
|
[ObservableProperty] private string? _responseStatus = null;
|
||||||
[ObservableProperty] private string? _responseAuthentication = null;
|
[ObservableProperty] private string? _responseAuthentication = null;
|
||||||
|
|
||||||
public event EventHandler<TextUpdatedEventArgs>? ResponseBodyUpdated;
|
public WindowService WindowService { get; }
|
||||||
|
|
||||||
public ConsoleViewModel() : base("Console", Material.Icons.MaterialIconKind.Console, -100)
|
public ConsoleViewModel(WindowService windowService) : base("Console", Material.Icons.MaterialIconKind.Console, -200)
|
||||||
{
|
{
|
||||||
|
WindowService = windowService;
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Register<OopsiesWindowRequestedMessage, string>(this, nameof(ConsoleView));
|
||||||
|
WeakReferenceMessenger.Default.Register<DataReadyMessage>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -31,6 +43,7 @@ namespace Needlework.Net.Desktop.ViewModels
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
IsRequestBusy = true;
|
||||||
if (string.IsNullOrEmpty(RequestPath)) throw new Exception("Path is empty.");
|
if (string.IsNullOrEmpty(RequestPath)) throw new Exception("Path is empty.");
|
||||||
|
|
||||||
var method = RequestMethodSelected switch
|
var method = RequestMethodSelected switch
|
||||||
@@ -47,29 +60,43 @@ namespace Needlework.Net.Desktop.ViewModels
|
|||||||
};
|
};
|
||||||
|
|
||||||
var processInfo = Connector.GetProcessInfo();
|
var processInfo = Connector.GetProcessInfo();
|
||||||
var response = await Connector.SendAsync(method, RequestPath) ?? throw new Exception("Response is null.");
|
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "ConsoleRequestEditor").Response;
|
||||||
|
var content = new StringContent(Regex.Replace(requestBody, @"\s+", ""), new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||||
|
var response = await Connector.SendAsync(method, RequestPath, content) ?? throw new Exception("Response is null.");
|
||||||
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||||
var body = await response.Content.ReadAsStringAsync();
|
var body = await response.Content.ReadAsStringAsync();
|
||||||
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
ResponseStatus = response.StatusCode.ToString();
|
ResponseStatus = response.StatusCode.ToString();
|
||||||
ResponsePath = $"https://127.0.0.1/{processInfo.AppPort}{RequestPath}";
|
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{RequestPath}";
|
||||||
ResponseAuthentication = riotAuthentication.Value;
|
ResponseAuthentication = $"Basic {riotAuthentication.Value}";
|
||||||
ResponseBodyUpdated?.Invoke(this, new(body));
|
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(body), nameof(ConsoleViewModel));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await SukiHost.ShowToast("Request Failed", ex.Message, SukiUI.Enums.NotificationType.Error);
|
await SukiHost.ShowToast("Request Failed", ex.Message, SukiUI.Enums.NotificationType.Error);
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
ResponseStatus = null;
|
ResponseStatus = null;
|
||||||
ResponsePath = null;
|
ResponsePath = null;
|
||||||
ResponseAuthentication = null;
|
ResponseAuthentication = null;
|
||||||
ResponseBodyUpdated?.Invoke(this, new(string.Empty));
|
WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(string.Empty), nameof(ConsoleViewModel));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsRequestBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(OopsiesWindowRequestedMessage message)
|
||||||
|
{
|
||||||
|
WindowService.ShowOopsiesWindow(message.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(DataReadyMessage message)
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||||
|
{
|
||||||
|
RequestPaths = new AvaloniaList<string>([.. message.Value.Paths]);
|
||||||
|
IsBusy = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using SukiUI.Controls;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop.ViewModels
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
{
|
{
|
||||||
public partial class EndpointViewModel(string endpoint) : ObservableObject
|
public partial class EndpointViewModel : ObservableObject, ISukiStackPageTitleProvider
|
||||||
{
|
{
|
||||||
public string Endpoint { get; } = endpoint;
|
public string Endpoint { get; }
|
||||||
public string Title => $"Needlework.Net - {Endpoint}";
|
public string Title => Endpoint;
|
||||||
|
|
||||||
|
[ObservableProperty] private IAvaloniaReadOnlyList<PathOperationViewModel> _pathOperations;
|
||||||
|
[ObservableProperty] private PathOperationViewModel? _selectedPathOperation;
|
||||||
|
|
||||||
|
public EndpointViewModel(string endpoint)
|
||||||
|
{
|
||||||
|
Endpoint = endpoint;
|
||||||
|
|
||||||
|
var handler = WeakReferenceMessenger.Default.Send<DataRequestMessage>().Response;
|
||||||
|
PathOperations = new AvaloniaList<PathOperationViewModel>(handler.Plugins[endpoint].Select(x => new PathOperationViewModel(x)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using SukiUI.Controls;
|
||||||
|
using System.Net.Http;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
|
{
|
||||||
|
public partial class EndpointsContainerViewModel : PageBase
|
||||||
|
{
|
||||||
|
[ObservableProperty] private ISukiStackPageTitleProvider _activeViewModel;
|
||||||
|
|
||||||
|
public EndpointsContainerViewModel(HttpClient httpClient) : base("Endpoints", Material.Icons.MaterialIconKind.Hub, -500)
|
||||||
|
{
|
||||||
|
_activeViewModel = new EndpointsViewModel(httpClient, OnClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClicked(ISukiStackPageTitleProvider viewModel)
|
||||||
|
{
|
||||||
|
ActiveViewModel = viewModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,57 +1,55 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using Avalonia.Collections;
|
||||||
using Needlework.Net.Core;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Needlework.Net.Desktop.Services;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using System.Collections.Generic;
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using SukiUI.Controls;
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop.ViewModels
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
{
|
{
|
||||||
public partial class EndpointsViewModel : PageBase
|
public partial class EndpointsViewModel : ObservableObject, IRecipient<DataReadyMessage>, ISukiStackPageTitleProvider
|
||||||
{
|
{
|
||||||
public HttpClient HttpClient { get; }
|
public HttpClient HttpClient { get; }
|
||||||
|
|
||||||
public DialogService DialogService { get; }
|
public string Title => "Endpoints";
|
||||||
|
public Action<ISukiStackPageTitleProvider> OnClicked;
|
||||||
|
|
||||||
[ObservableProperty] private List<string> _plugins = [];
|
[ObservableProperty] private IAvaloniaReadOnlyList<string> _plugins = new AvaloniaList<string>();
|
||||||
[ObservableProperty] private bool _isBusy = true;
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
[ObservableProperty] private string _search = string.Empty;
|
[ObservableProperty] private string _search = string.Empty;
|
||||||
[ObservableProperty] private List<string> _query = [];
|
[ObservableProperty] private IAvaloniaReadOnlyList<string> _query = new AvaloniaList<string>();
|
||||||
[ObservableProperty] private string? _selectedQuery = string.Empty;
|
[ObservableProperty] private string? _selectedQuery = string.Empty;
|
||||||
|
|
||||||
public EndpointsViewModel(HttpClient httpClient, DialogService dialogService) : base("Endpoints", Material.Icons.MaterialIconKind.Hub, -500)
|
public EndpointsViewModel(HttpClient httpClient, Action<ISukiStackPageTitleProvider> onClicked)
|
||||||
{
|
{
|
||||||
HttpClient = httpClient;
|
HttpClient = httpClient;
|
||||||
DialogService = dialogService;
|
OnClicked = onClicked;
|
||||||
|
|
||||||
Task.Run(InitializeAsync);
|
WeakReferenceMessenger.Default.Register(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InitializeAsync()
|
public void Receive(DataReadyMessage message)
|
||||||
{
|
{
|
||||||
var handler = new LcuSchemaHandler(await Resources.GetOpenApiDocumentAsync(HttpClient));
|
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
Plugins = [.. handler.Plugins.Keys];
|
|
||||||
Query = [.. Plugins];
|
|
||||||
IsBusy = false;
|
IsBusy = false;
|
||||||
});
|
Plugins = new AvaloniaList<string>([.. message.Value.Plugins.Keys]);
|
||||||
|
Query = new AvaloniaList<string>([.. Plugins]);
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSearchChanged(string value)
|
partial void OnSearchChanged(string value)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(Search))
|
if (!string.IsNullOrEmpty(Search))
|
||||||
Query = Plugins.Where(x => x.Contains(value)).ToList();
|
Query = new AvaloniaList<string>(Plugins.Where(x => x.Contains(value)));
|
||||||
else
|
else
|
||||||
Query = Plugins;
|
Query = Plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void OnSelectedQueryChanged(string? value)
|
partial void OnSelectedQueryChanged(string? value)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(value))
|
if (string.IsNullOrEmpty(value)) return;
|
||||||
return;
|
|
||||||
DialogService.ShowEndpoint(value);
|
OnClicked.Invoke(new EndpointViewModel(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Threading.Tasks;
|
using System.Threading;
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop.ViewModels
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
{
|
{
|
||||||
@@ -16,27 +16,40 @@ namespace Needlework.Net.Desktop.ViewModels
|
|||||||
|
|
||||||
public HomeViewModel() : base("Home", Material.Icons.MaterialIconKind.Home, int.MinValue)
|
public HomeViewModel() : base("Home", Material.Icons.MaterialIconKind.Home, int.MinValue)
|
||||||
{
|
{
|
||||||
Task.Run(async () => { while (true) { SetStatus(); await Task.Delay(TimeSpan.FromSeconds(5)); } });
|
StartProcessing();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetStatus()
|
private void StartProcessing()
|
||||||
|
{
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
while (true)
|
||||||
{
|
{
|
||||||
void Set(string text, Color color, string address)
|
void Set(string text, Color color, string address)
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||||
{
|
{
|
||||||
StatusText = text;
|
StatusText = text;
|
||||||
StatusForeground = new SolidColorBrush(color.ToUInt32());
|
StatusForeground = new SolidColorBrush(color.ToUInt32());
|
||||||
StatusAddress = address;
|
StatusAddress = address;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var processInfo = Connector.GetProcessInfo();
|
var processInfo = Connector.GetProcessInfo();
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() => Set("Online", Colors.Green, $"https://127.0.0.1:{processInfo.AppPort}/"));
|
Set("Online", Colors.Green, $"https://127.0.0.1:{processInfo.AppPort}/");
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException)
|
catch (InvalidOperationException)
|
||||||
{
|
{
|
||||||
Avalonia.Threading.Dispatcher.UIThread.Post(() => Set("Offline", Colors.Red, "N/A"));
|
Set("Offline", Colors.Red, "N/A");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Thread.Sleep(TimeSpan.FromSeconds(5));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{ IsBackground = true };
|
||||||
|
thread.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|||||||
@@ -1,22 +1,60 @@
|
|||||||
using Avalonia.Collections;
|
using Avalonia.Collections;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Needlework.Net.Core;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using SukiUI.Controls;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop.ViewModels
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
{
|
{
|
||||||
public partial class MainWindowViewModel : ObservableObject
|
public partial class MainWindowViewModel : ObservableObject, IRecipient<DataRequestMessage>, IRecipient<HostDocumentRequestMessage>
|
||||||
{
|
{
|
||||||
public IAvaloniaReadOnlyList<PageBase> Pages { get; }
|
public IAvaloniaReadOnlyList<PageBase> Pages { get; }
|
||||||
|
|
||||||
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
public string Version { get; } = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0.0.0.0";
|
||||||
|
|
||||||
public MainWindowViewModel(IEnumerable<PageBase> pages)
|
public HttpClient HttpClient { get; }
|
||||||
|
public LcuSchemaHandler? LcuSchemaHandler { get; set; }
|
||||||
|
public OpenApiDocument? HostDocument { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _isBusy = true;
|
||||||
|
|
||||||
|
public MainWindowViewModel(IEnumerable<PageBase> pages, HttpClient httpClient)
|
||||||
{
|
{
|
||||||
Pages = new AvaloniaList<PageBase>(pages.OrderBy(x => x.Index).ThenBy(x => x.DisplayName));
|
Pages = new AvaloniaList<PageBase>(pages.OrderBy(x => x.Index).ThenBy(x => x.DisplayName));
|
||||||
|
HttpClient = httpClient;
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||||
|
Task.Run(FetchDataAsync);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task FetchDataAsync()
|
||||||
|
{
|
||||||
|
var document = await Resources.GetOpenApiDocumentAsync(HttpClient);
|
||||||
|
HostDocument = document;
|
||||||
|
var handler = new LcuSchemaHandler(document);
|
||||||
|
LcuSchemaHandler = handler;
|
||||||
|
WeakReferenceMessenger.Default.Send(new DataReadyMessage(handler));
|
||||||
|
await Avalonia.Threading.Dispatcher.UIThread.InvokeAsync(async () => await SukiHost.ShowToast("OpenAPI Data Processed", "Some pages can now be used.", SukiUI.Enums.NotificationType.Success, TimeSpan.FromSeconds(5)));
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(DataRequestMessage message)
|
||||||
|
{
|
||||||
|
message.Reply(LcuSchemaHandler!);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(HostDocumentRequestMessage message)
|
||||||
|
{
|
||||||
|
message.Reply(HostDocument!);
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
|
|||||||
29
Needlework.Net.Desktop/ViewModels/OopsiesWindowViewModel.cs
Normal file
29
Needlework.Net.Desktop/ViewModels/OopsiesWindowViewModel.cs
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
|
{
|
||||||
|
public partial class OopsiesWindowViewModel(string text) : ObservableObject
|
||||||
|
{
|
||||||
|
public string Text { get; } = text;
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void OpenDefaultEditor()
|
||||||
|
{
|
||||||
|
var temp = Path.GetTempFileName().Replace(".tmp", ".json");
|
||||||
|
File.WriteAllText(temp, Text);
|
||||||
|
Process.Start("explorer", "\"" + temp + "\"");
|
||||||
|
CloseDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void CloseDialog()
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new OopsiesWindowCanceledMessage(null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
153
Needlework.Net.Desktop/ViewModels/OperationViewModel.cs
Normal file
153
Needlework.Net.Desktop/ViewModels/OperationViewModel.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
|
{
|
||||||
|
public partial class OperationViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public string Summary { get; }
|
||||||
|
public string Description { get; }
|
||||||
|
public string ReturnType { get; }
|
||||||
|
public bool IsRequestBody { get; }
|
||||||
|
public string? RequestBodyType { get; }
|
||||||
|
public IAvaloniaReadOnlyList<PropertyClassViewModel> RequestClasses { get; }
|
||||||
|
public IAvaloniaReadOnlyList<PropertyClassViewModel> ResponseClasses { get; }
|
||||||
|
public IAvaloniaReadOnlyList<ParameterViewModel> PathParameters { get; }
|
||||||
|
public IAvaloniaReadOnlyList<ParameterViewModel> QueryParameters { get; }
|
||||||
|
|
||||||
|
public OperationViewModel(OpenApiOperation operation)
|
||||||
|
{
|
||||||
|
Summary = operation.Summary ?? string.Empty;
|
||||||
|
Description = operation.Description ?? string.Empty;
|
||||||
|
IsRequestBody = operation.RequestBody != null;
|
||||||
|
ReturnType = GetReturnType(operation.Responses);
|
||||||
|
RequestClasses = GetRequestClasses(operation.RequestBody);
|
||||||
|
ResponseClasses = GetResponseClasses(operation.Responses);
|
||||||
|
PathParameters = GetParameters(operation.Parameters, ParameterLocation.Path);
|
||||||
|
QueryParameters = GetParameters(operation.Parameters, ParameterLocation.Query);
|
||||||
|
RequestBodyType = GetRequestBodyType(operation.RequestBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetRequestBodyType(OpenApiRequestBody? requestBody)
|
||||||
|
{
|
||||||
|
if (requestBody == null) return null;
|
||||||
|
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||||
|
{
|
||||||
|
var schema = media.Schema;
|
||||||
|
return GetSchemaType(schema);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AvaloniaList<ParameterViewModel> GetParameters(IList<OpenApiParameter> parameters, ParameterLocation location)
|
||||||
|
{
|
||||||
|
var pathParameters = new AvaloniaList<ParameterViewModel>();
|
||||||
|
foreach (var parameter in parameters)
|
||||||
|
{
|
||||||
|
if (parameter.In != location) break;
|
||||||
|
pathParameters.Add(new ParameterViewModel(parameter.Name, parameter.Schema.Type, parameter.Required));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private AvaloniaList<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses)
|
||||||
|
{
|
||||||
|
if (responses.TryGetValue("2XX", out var response)
|
||||||
|
&& response.Content.TryGetValue("application/json", out var media))
|
||||||
|
{
|
||||||
|
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
|
||||||
|
var schema = media.Schema;
|
||||||
|
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
||||||
|
WalkSchema(schema, propertyClasses, document);
|
||||||
|
return propertyClasses;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WalkSchema(OpenApiSchema schema, AvaloniaList<PropertyClassViewModel> propertyClasses, OpenApiDocument document)
|
||||||
|
{
|
||||||
|
var type = GetSchemaType(schema);
|
||||||
|
if (IsComponent(type))
|
||||||
|
{
|
||||||
|
string componentId = GetComponentId(schema);
|
||||||
|
var componentSchema = document.Components.Schemas[componentId];
|
||||||
|
var responseClass = new PropertyClassViewModel(componentId, componentSchema.Properties, componentSchema.Enum);
|
||||||
|
propertyClasses.Add(responseClass);
|
||||||
|
|
||||||
|
foreach ((var _, var property) in componentSchema.Properties)
|
||||||
|
// Check for self-references like "LolLootLootOddsResponse"
|
||||||
|
// I blame dubble
|
||||||
|
if (IsComponent(GetSchemaType(property)) && componentId != GetComponentId(property))
|
||||||
|
WalkSchema(property, propertyClasses, document);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetComponentId(OpenApiSchema schema)
|
||||||
|
{
|
||||||
|
string componentId;
|
||||||
|
if (schema.Reference != null) componentId = schema.Reference.Id;
|
||||||
|
else if (schema.Items != null) componentId = schema.Items.Reference.Id;
|
||||||
|
else componentId = schema.AdditionalProperties.Reference.Id;
|
||||||
|
return componentId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsComponent(string type)
|
||||||
|
{
|
||||||
|
return !(type.Contains("object")
|
||||||
|
|| type.Contains("array")
|
||||||
|
|| type.Contains("bool")
|
||||||
|
|| type.Contains("string")
|
||||||
|
|| type.Contains("integer")
|
||||||
|
|| type.Contains("number"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AvaloniaList<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody)
|
||||||
|
{
|
||||||
|
if (requestBody == null) return [];
|
||||||
|
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||||
|
{
|
||||||
|
var document = WeakReferenceMessenger.Default.Send(new HostDocumentRequestMessage()).Response;
|
||||||
|
var schema = media.Schema;
|
||||||
|
if (schema == null) return [];
|
||||||
|
|
||||||
|
var type = GetSchemaType(media.Schema);
|
||||||
|
if (IsComponent(type))
|
||||||
|
{
|
||||||
|
var componentId = GetComponentId(schema);
|
||||||
|
var componentSchema = document.Components.Schemas[componentId];
|
||||||
|
AvaloniaList<PropertyClassViewModel> propertyClasses = [];
|
||||||
|
WalkSchema(componentSchema, propertyClasses, document);
|
||||||
|
return propertyClasses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetReturnType(OpenApiResponses responses)
|
||||||
|
{
|
||||||
|
if (responses.TryGetValue("2XX", out var response)
|
||||||
|
&& response.Content.TryGetValue("application/json", out var media))
|
||||||
|
{
|
||||||
|
var schema = media.Schema;
|
||||||
|
return GetSchemaType(schema);
|
||||||
|
}
|
||||||
|
return "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetSchemaType(OpenApiSchema schema)
|
||||||
|
{
|
||||||
|
if (schema.Reference != null) return schema.Reference.Id;
|
||||||
|
if (schema.Type == "object" && schema.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||||
|
if (schema.Type == "integer" || schema.Type == "number") return $"{schema.Type}:{schema.Format}";
|
||||||
|
if (schema.Type == "array" && schema.Items.Reference != null) return $"{schema.Items.Reference.Id}[]";
|
||||||
|
if (schema.Type == "array" && (schema.Items.Type == "integer" || schema.Items.Type == "number")) return $"{schema.Items.Type}:{schema.Items.Format}[]";
|
||||||
|
if (schema.Type == "array") return $"{schema.Items.Type}[]";
|
||||||
|
return schema.Type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
Needlework.Net.Desktop/ViewModels/ParameterViewModel.cs
Normal file
20
Needlework.Net.Desktop/ViewModels/ParameterViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
|
{
|
||||||
|
public partial class ParameterViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public string Type { get; }
|
||||||
|
public bool IsRequired { get; }
|
||||||
|
[ObservableProperty] private string? _value = null;
|
||||||
|
|
||||||
|
public ParameterViewModel(string name, string type, bool isRequired, string? value = null)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
IsRequired = isRequired;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
134
Needlework.Net.Desktop/ViewModels/PathOperationViewModel.cs
Normal file
134
Needlework.Net.Desktop/ViewModels/PathOperationViewModel.cs
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
using Avalonia.Media;
|
||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Core;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using SukiUI.Controls;
|
||||||
|
using System;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
|
{
|
||||||
|
public partial class PathOperationViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public string Method { get; }
|
||||||
|
public SolidColorBrush Color { get; }
|
||||||
|
public string Path { get; }
|
||||||
|
public OperationViewModel Operation { get; }
|
||||||
|
public ProcessInfo? ProcessInfo { get; }
|
||||||
|
|
||||||
|
[ObservableProperty] private bool _isBusy;
|
||||||
|
[ObservableProperty] private string? _responsePath;
|
||||||
|
[ObservableProperty] private string? _responseStatus;
|
||||||
|
[ObservableProperty] private string? _responseAuthentication;
|
||||||
|
[ObservableProperty] private string? _responseUsername;
|
||||||
|
[ObservableProperty] private string? _responsePassword;
|
||||||
|
[ObservableProperty] private string? _responseAuthorization;
|
||||||
|
|
||||||
|
public PathOperationViewModel(PathOperation pathOperation)
|
||||||
|
{
|
||||||
|
Method = pathOperation.Method.ToUpper();
|
||||||
|
Color = new SolidColorBrush(GetColor(pathOperation.Method.ToUpper()));
|
||||||
|
Path = pathOperation.Path;
|
||||||
|
Operation = new OperationViewModel(pathOperation.Operation);
|
||||||
|
ProcessInfo = GetProcessInfo();
|
||||||
|
ResponsePath = ProcessInfo != null ? $"https://127.0.0.1:{ProcessInfo.AppPort}{Path}" : null;
|
||||||
|
ResponseUsername = ProcessInfo != null ? new RiotAuthentication(ProcessInfo.RemotingAuthToken).Username : null;
|
||||||
|
ResponsePassword = ProcessInfo != null ? new RiotAuthentication(ProcessInfo.RemotingAuthToken).Password : null;
|
||||||
|
ResponseAuthorization = ProcessInfo != null ? $"Basic {new RiotAuthentication(ProcessInfo.RemotingAuthToken).Value}" : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProcessInfo? GetProcessInfo()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var processInfo = Connector.GetProcessInfo();
|
||||||
|
return processInfo;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Task.Run(async () => await SukiHost.ShowToast("Error", ex.Message, SukiUI.Enums.NotificationType.Error));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task SendRequest()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
IsBusy = true;
|
||||||
|
|
||||||
|
var method = Method.ToUpper() switch
|
||||||
|
{
|
||||||
|
"GET" => HttpMethod.Get,
|
||||||
|
"POST" => HttpMethod.Post,
|
||||||
|
"PUT" => HttpMethod.Put,
|
||||||
|
"DELETE" => HttpMethod.Delete,
|
||||||
|
"HEAD" => HttpMethod.Head,
|
||||||
|
"PATCH" => HttpMethod.Patch,
|
||||||
|
"OPTIONS" => HttpMethod.Options,
|
||||||
|
"TRACE" => HttpMethod.Trace,
|
||||||
|
_ => throw new Exception("Method is missing.")
|
||||||
|
};
|
||||||
|
|
||||||
|
var processInfo = Connector.GetProcessInfo();
|
||||||
|
var path = Path;
|
||||||
|
foreach (var pathParameter in Operation.PathParameters)
|
||||||
|
{
|
||||||
|
path = path.Replace($"{{{pathParameter.Name}}}", pathParameter.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var query = "";
|
||||||
|
foreach (var queryParameter in Operation.QueryParameters)
|
||||||
|
{
|
||||||
|
if (query.Length != 0 && !string.IsNullOrWhiteSpace(queryParameter.Value))
|
||||||
|
query += $"&{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}";
|
||||||
|
else if (query.Length == 0 && !string.IsNullOrWhiteSpace(queryParameter.Value))
|
||||||
|
query += $"?{queryParameter.Name}={Uri.EscapeDataString(queryParameter.Value)}";
|
||||||
|
}
|
||||||
|
var uri = $"{path}{query}";
|
||||||
|
|
||||||
|
var requestBody = WeakReferenceMessenger.Default.Send(new ContentRequestMessage(), "EndpointRequestEditor").Response;
|
||||||
|
var content = new StringContent(Regex.Replace(requestBody, @"\s+", ""), new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"));
|
||||||
|
|
||||||
|
var response = await Connector.SendAsync(method, $"{uri}", content) ?? throw new Exception("Response is null.");
|
||||||
|
var riotAuthentication = new RiotAuthentication(processInfo.RemotingAuthToken);
|
||||||
|
var responseBody = await response.Content.ReadAsStringAsync();
|
||||||
|
responseBody = !string.IsNullOrEmpty(responseBody) ? JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(responseBody), App.JsonSerializerOptions) : string.Empty;
|
||||||
|
|
||||||
|
ResponseStatus = $"{(int)response.StatusCode} {response.StatusCode}";
|
||||||
|
ResponsePath = $"https://127.0.0.1:{processInfo.AppPort}{uri}";
|
||||||
|
ResponseAuthentication = $"Basic {riotAuthentication.Value}";
|
||||||
|
ResponseUsername = riotAuthentication.Username;
|
||||||
|
ResponsePassword = riotAuthentication.Password;
|
||||||
|
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(responseBody, "EndpointResponseEditor")));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await SukiHost.ShowToast("Request Failed", ex.Message, SukiUI.Enums.NotificationType.Error);
|
||||||
|
WeakReferenceMessenger.Default.Send(new EditorUpdateMessage(new(string.Empty, "EndpointResponseEditor")));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsBusy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color GetColor(string method) => method switch
|
||||||
|
{
|
||||||
|
"GET" => Avalonia.Media.Color.FromRgb(95, 99, 186),
|
||||||
|
"POST" => Avalonia.Media.Color.FromRgb(103, 186, 95),
|
||||||
|
"PUT" => Avalonia.Media.Color.FromRgb(186, 139, 95),
|
||||||
|
"DELETE" => Avalonia.Media.Color.FromRgb(186, 95, 95),
|
||||||
|
"HEAD" => Avalonia.Media.Color.FromRgb(136, 95, 186),
|
||||||
|
"PATCH" => Avalonia.Media.Color.FromRgb(95, 186, 139),
|
||||||
|
_ => throw new InvalidOperationException("Method does not have assigned color.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace Needlework.Net.Desktop.ViewModels
|
|
||||||
{
|
|
||||||
public class PluginViewModel
|
|
||||||
{
|
|
||||||
public PluginViewModel() { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
36
Needlework.Net.Desktop/ViewModels/PropertyClassViewModel.cs
Normal file
36
Needlework.Net.Desktop/ViewModels/PropertyClassViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Microsoft.OpenApi.Any;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
|
{
|
||||||
|
public class PropertyClassViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public string Id { get; }
|
||||||
|
public IAvaloniaReadOnlyList<PropertyFieldViewModel> PropertyFields { get; } = new AvaloniaList<PropertyFieldViewModel>();
|
||||||
|
public IAvaloniaReadOnlyList<PropertyEnumViewModel> PropertyEnums { get; } = new AvaloniaList<PropertyEnumViewModel>();
|
||||||
|
|
||||||
|
public PropertyClassViewModel(string id, IDictionary<string, OpenApiSchema> properties, IList<IOpenApiAny> enumValue)
|
||||||
|
{
|
||||||
|
AvaloniaList<PropertyFieldViewModel> propertyFields = [];
|
||||||
|
AvaloniaList<PropertyEnumViewModel> propertyEnums = [];
|
||||||
|
foreach ((var propertyName, var propertySchema) in properties)
|
||||||
|
{
|
||||||
|
var type = OperationViewModel.GetSchemaType(propertySchema);
|
||||||
|
var field = new PropertyFieldViewModel(propertyName, type);
|
||||||
|
propertyFields.Add(field);
|
||||||
|
}
|
||||||
|
if (enumValue != null && enumValue.Any())
|
||||||
|
{
|
||||||
|
var propertyEnum = new PropertyEnumViewModel(enumValue);
|
||||||
|
propertyEnums.Add(propertyEnum);
|
||||||
|
}
|
||||||
|
PropertyFields = propertyFields;
|
||||||
|
PropertyEnums = propertyEnums;
|
||||||
|
Id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Needlework.Net.Desktop/ViewModels/PropertyEnumViewModel.cs
Normal file
17
Needlework.Net.Desktop/ViewModels/PropertyEnumViewModel.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Microsoft.OpenApi.Any;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
|
{
|
||||||
|
public class PropertyEnumViewModel
|
||||||
|
{
|
||||||
|
public string Type { get; } = "Enum";
|
||||||
|
public string Values { get; }
|
||||||
|
|
||||||
|
public PropertyEnumViewModel(IList<IOpenApiAny> enumValue)
|
||||||
|
{
|
||||||
|
Values = $"[{string.Join(", ", enumValue.Select(x => ((OpenApiString)x).Value).ToList())}]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Needlework.Net.Desktop/ViewModels/PropertyFieldViewModel.cs
Normal file
14
Needlework.Net.Desktop/ViewModels/PropertyFieldViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
|
{
|
||||||
|
public class PropertyFieldViewModel
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public string Type { get; }
|
||||||
|
|
||||||
|
public PropertyFieldViewModel(string name, string type)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
103
Needlework.Net.Desktop/ViewModels/WebsocketViewModel.cs
Normal file
103
Needlework.Net.Desktop/ViewModels/WebsocketViewModel.cs
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
using BlossomiShymae.GrrrLCU;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Material.Icons;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using Needlework.Net.Desktop.Services;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Websocket.Client;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.ViewModels
|
||||||
|
{
|
||||||
|
public partial class WebsocketViewModel : PageBase
|
||||||
|
{
|
||||||
|
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
|
||||||
|
[ObservableProperty] private ObservableCollection<string> _eventLog = [];
|
||||||
|
[NotifyPropertyChangedFor(nameof(FilteredEventLog))]
|
||||||
|
[ObservableProperty] private string _search = string.Empty;
|
||||||
|
[ObservableProperty] private bool _isAttach = true;
|
||||||
|
[ObservableProperty] private bool _isTail = false;
|
||||||
|
[ObservableProperty] private string? _selectedEventLog = null;
|
||||||
|
|
||||||
|
private Dictionary<string, EventMessage> _events = [];
|
||||||
|
|
||||||
|
public WindowService WindowService { get; }
|
||||||
|
|
||||||
|
public List<string> FilteredEventLog => string.IsNullOrWhiteSpace(Search) ? [.. EventLog] : [.. EventLog.Where(x => x.ToLower().Contains(Search.ToLower()))];
|
||||||
|
|
||||||
|
public WebsocketViewModel(WindowService windowService) : base("Event Viewer", MaterialIconKind.Connection, -100)
|
||||||
|
{
|
||||||
|
WindowService = windowService;
|
||||||
|
|
||||||
|
var client = Connector.CreateLcuWebsocketClient();
|
||||||
|
client.EventReceived.Subscribe(OnMessage);
|
||||||
|
client.DisconnectionHappened.Subscribe(OnDisconnection);
|
||||||
|
client.ReconnectionHappened.Subscribe(OnReconnection);
|
||||||
|
|
||||||
|
client.Start();
|
||||||
|
client.Send(new EventMessage(RequestType.Subscribe, EventMessage.Kinds.OnJsonApiEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private void Clear()
|
||||||
|
{
|
||||||
|
EventLog = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedEventLogChanged(string? value)
|
||||||
|
{
|
||||||
|
if (value == null) return;
|
||||||
|
if (_events.TryGetValue(value, out var message))
|
||||||
|
{
|
||||||
|
var text = JsonSerializer.Serialize(message, App.JsonSerializerOptions);
|
||||||
|
if (text.Length >= App.MaxCharacters) WindowService.ShowOopsiesWindow(text);
|
||||||
|
else WeakReferenceMessenger.Default.Send(new ResponseUpdatedMessage(text), nameof(WebsocketViewModel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnReconnection(ReconnectionInfo info)
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"-- Reconnection --\n{JsonSerializer.Serialize(info, App.JsonSerializerOptions)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisconnection(DisconnectionInfo info)
|
||||||
|
{
|
||||||
|
Trace.WriteLine($"-- Disconnection --\n{JsonSerializer.Serialize(info, App.JsonSerializerOptions)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMessage(EventMessage message)
|
||||||
|
{
|
||||||
|
Avalonia.Threading.Dispatcher.UIThread.Invoke(() =>
|
||||||
|
{
|
||||||
|
if (!IsAttach) return;
|
||||||
|
|
||||||
|
var line = $"{DateTime.Now:HH:mm:ss.fff} {message.Data?.EventType.ToUpper()} {message.Data?.Uri}";
|
||||||
|
var log = EventLog.ToList();
|
||||||
|
Trace.WriteLine($"Message: {line}");
|
||||||
|
if (log.Count < 1000)
|
||||||
|
{
|
||||||
|
log.Add(line);
|
||||||
|
_events[line] = message;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var key = $"{log[0]}";
|
||||||
|
log.RemoveAt(0);
|
||||||
|
_events.Remove(key);
|
||||||
|
|
||||||
|
log.Add(line);
|
||||||
|
_events[line] = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
EventLog = []; // This is a hack needed to update for ListBox
|
||||||
|
EventLog = new ObservableCollection<string>(log);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,8 +8,10 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Desktop.Views.AboutView"
|
x:Class="Needlework.Net.Desktop.Views.AboutView"
|
||||||
x:DataType="vm:AboutViewModel">
|
x:DataType="vm:AboutViewModel">
|
||||||
<ScrollViewer>
|
<Grid Margin="8"
|
||||||
<WrapPanel Margin="8"
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
<WrapPanel
|
||||||
theme:WrapPanelExtensions.AnimatedScroll="true"
|
theme:WrapPanelExtensions.AnimatedScroll="true"
|
||||||
Orientation="Horizontal">
|
Orientation="Horizontal">
|
||||||
<suki:GlassCard Margin="8">
|
<suki:GlassCard Margin="8">
|
||||||
@@ -27,7 +29,7 @@
|
|||||||
<suki:GlassCard Width="400" Margin="8">
|
<suki:GlassCard Width="400" Margin="8">
|
||||||
<suki:GroupBox Header="About">
|
<suki:GroupBox Header="About">
|
||||||
<TextBlock TextWrapping="Wrap">
|
<TextBlock TextWrapping="Wrap">
|
||||||
Needlework.Net is the sister project of Needlework. Like Needlework, this project is inspired by
|
Needlework.Net is .NET rewrite of Needlework. Like Needlework, this project is inspired by
|
||||||
LCU Explorer. This tool was made to help others with LCU development. Feel free to ask any questions
|
LCU Explorer. This tool was made to help others with LCU development. Feel free to ask any questions
|
||||||
or help contribute to the project! 💜
|
or help contribute to the project! 💜
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
@@ -35,5 +37,5 @@
|
|||||||
</suki:GlassCard>
|
</suki:GlassCard>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</WrapPanel>
|
</WrapPanel>
|
||||||
</ScrollViewer>
|
</Grid>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Desktop.Views.ConsoleView"
|
x:Class="Needlework.Net.Desktop.Views.ConsoleView"
|
||||||
x:DataType="vm:ConsoleViewModel">
|
x:DataType="vm:ConsoleViewModel">
|
||||||
|
<suki:BusyArea IsBusy="{Binding IsBusy}" BusyText="Loading...">
|
||||||
<Grid Margin="16" RowDefinitions="auto,*" ColumnDefinitions="*,*">
|
<Grid Margin="16" RowDefinitions="auto,*" ColumnDefinitions="*,*">
|
||||||
<Grid Grid.Row="0"
|
<Grid Grid.Row="0"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
@@ -18,11 +19,23 @@
|
|||||||
<Grid RowDefinitions="auto,auto" ColumnDefinitions="auto,*">
|
<Grid RowDefinitions="auto,auto" ColumnDefinitions="auto,*">
|
||||||
<ComboBox ItemsSource="{Binding RequestMethods}" SelectedItem="{Binding RequestMethodSelected}"
|
<ComboBox ItemsSource="{Binding RequestMethods}" SelectedItem="{Binding RequestMethodSelected}"
|
||||||
Grid.Row="0" Grid.Column="0"/>
|
Grid.Row="0" Grid.Column="0"/>
|
||||||
<TextBox Text="{Binding RequestPath}"
|
<AutoCompleteBox
|
||||||
Grid.Row="0" Grid.Column="1"
|
ItemsSource="{Binding RequestPaths}"
|
||||||
Watermark="E.g. /lol-summoner/v1/current-summoner"/>
|
Text="{Binding RequestPath}"
|
||||||
<TextBox Text="{Binding RequestBody}" Height="200" AcceptsReturn="True" TextWrapping="Wrap"
|
MaxDropDownHeight="400"
|
||||||
Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"/>
|
FilterMode="StartsWith"
|
||||||
|
Grid.Row="0" Grid.Column="1"/>
|
||||||
|
<avaloniaEdit:TextEditor
|
||||||
|
Name="RequestEditor"
|
||||||
|
Text=""
|
||||||
|
ShowLineNumbers="True"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollBarVisibility="Visible"
|
||||||
|
FontSize="12"
|
||||||
|
Height="100"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</suki:GroupBox>
|
</suki:GroupBox>
|
||||||
</suki:GlassCard>
|
</suki:GlassCard>
|
||||||
@@ -31,7 +44,8 @@
|
|||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
FontWeight="DemiBold"
|
FontWeight="DemiBold"
|
||||||
Command="{Binding SendRequestCommand}">
|
Command="{Binding SendRequestCommand}"
|
||||||
|
theme:ButtonExtensions.ShowProgress="{Binding IsRequestBusy}">
|
||||||
Send
|
Send
|
||||||
</Button>
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -40,26 +54,33 @@
|
|||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="0">
|
Grid.Column="0">
|
||||||
<suki:GlassCard Margin="0 4">
|
<suki:GlassCard Margin="0 4">
|
||||||
|
<suki:GroupBox Header="Path">
|
||||||
<TextBlock Text="{Binding ResponsePath}"/>
|
<TextBlock Text="{Binding ResponsePath}"/>
|
||||||
|
</suki:GroupBox>
|
||||||
</suki:GlassCard>
|
</suki:GlassCard>
|
||||||
<suki:GlassCard Margin="0 4">
|
<suki:GlassCard Margin="0 4">
|
||||||
|
<suki:GroupBox Header="Status">
|
||||||
<TextBlock Text="{Binding ResponseStatus}"/>
|
<TextBlock Text="{Binding ResponseStatus}"/>
|
||||||
|
</suki:GroupBox>
|
||||||
</suki:GlassCard>
|
</suki:GlassCard>
|
||||||
<suki:GlassCard Margin="0 4">
|
<suki:GlassCard Margin="0 4">
|
||||||
|
<suki:GroupBox Header="Authentication">
|
||||||
<TextBlock Text="{Binding ResponseAuthentication}" />
|
<TextBlock Text="{Binding ResponseAuthentication}" />
|
||||||
|
</suki:GroupBox>
|
||||||
</suki:GlassCard>
|
</suki:GlassCard>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<suki:GlassCard
|
<suki:GlassCard
|
||||||
Margin="0 8"
|
Margin="0 8"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Grid.Column="1">
|
Grid.Column="1">
|
||||||
<avaloniaEdit:TextEditor Name="ResponseEditor"
|
<avaloniaEdit:TextEditor
|
||||||
Text=""
|
Name="ResponseEditor"
|
||||||
FontFamily="Cascadia Code,Consolas,Menlo,Monospace"
|
|
||||||
HorizontalScrollBarVisibility="Auto"
|
HorizontalScrollBarVisibility="Auto"
|
||||||
VerticalScrollBarVisibility="Visible"
|
VerticalScrollBarVisibility="Visible"
|
||||||
FontWeight="Light"
|
ShowLineNumbers="True"
|
||||||
FontSize="14"/>
|
Text=""
|
||||||
|
FontSize="12"/>
|
||||||
</suki:GlassCard>
|
</suki:GlassCard>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
</suki:BusyArea>
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using AvaloniaEdit;
|
using AvaloniaEdit;
|
||||||
using AvaloniaEdit.Highlighting;
|
|
||||||
using AvaloniaEdit.Indentation.CSharp;
|
|
||||||
using AvaloniaEdit.TextMate;
|
using AvaloniaEdit.TextMate;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Desktop.Extensions;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
using Needlework.Net.Desktop.ViewModels;
|
using Needlework.Net.Desktop.ViewModels;
|
||||||
using SukiUI;
|
using SukiUI;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
@@ -13,35 +14,58 @@ using TextMateSharp.Grammars;
|
|||||||
|
|
||||||
namespace Needlework.Net.Desktop.Views;
|
namespace Needlework.Net.Desktop.Views;
|
||||||
|
|
||||||
public partial class ConsoleView : UserControl
|
public partial class ConsoleView : UserControl, IRecipient<ResponseUpdatedMessage>, IRecipient<ContentRequestMessage>
|
||||||
{
|
{
|
||||||
private TextEditor? _responseEditor;
|
private TextEditor? _responseEditor;
|
||||||
|
private TextEditor? _requestEditor;
|
||||||
|
|
||||||
public ConsoleView()
|
public ConsoleView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Receive(ResponseUpdatedMessage message)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(message.Value))
|
||||||
|
{
|
||||||
|
var text = JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(message.Value), App.JsonSerializerOptions);
|
||||||
|
if (text.Length >= App.MaxCharacters)
|
||||||
|
{
|
||||||
|
WeakReferenceMessenger.Default.Send(new OopsiesWindowRequestedMessage(text), nameof(ConsoleView));
|
||||||
|
_responseEditor!.Text = string.Empty;
|
||||||
|
}
|
||||||
|
else _responseEditor!.Text = text;
|
||||||
|
}
|
||||||
|
else _responseEditor!.Text = message.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(ContentRequestMessage message)
|
||||||
|
{
|
||||||
|
message.Reply(_requestEditor!.Text);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
|
|
||||||
_responseEditor = this.FindControl<TextEditor>("ResponseEditor");
|
_responseEditor = this.FindControl<TextEditor>("ResponseEditor");
|
||||||
_responseEditor!.TextArea.IndentationStrategy = new CSharpIndentationStrategy(_responseEditor.Options);
|
_requestEditor = this.FindControl<TextEditor>("RequestEditor");
|
||||||
_responseEditor!.TextArea.RightClickMovesCaret = true;
|
_responseEditor?.ApplyJsonEditorSettings();
|
||||||
_responseEditor!.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("JavaScript");
|
_requestEditor?.ApplyJsonEditorSettings();
|
||||||
|
|
||||||
((ConsoleViewModel)DataContext!)!.ResponseBodyUpdated += ConsoleView_ResponseBodyUpdated;
|
WeakReferenceMessenger.Default.Register<ResponseUpdatedMessage, string>(this, nameof(ConsoleViewModel));
|
||||||
|
WeakReferenceMessenger.Default.Register<ContentRequestMessage, string>(this, "ConsoleRequestEditor");
|
||||||
|
|
||||||
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||||
SukiTheme.GetInstance().OnBaseThemeChanged += OnBaseThemeChanged;
|
SukiTheme.GetInstance().OnBaseThemeChanged += OnBaseThemeChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConsoleView_ResponseBodyUpdated(object? sender, TextUpdatedEventArgs e)
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(e.Text))
|
base.OnDetachedFromVisualTree(e);
|
||||||
_responseEditor!.Text = JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(e.Text), App.JsonSerializerOptions);
|
|
||||||
else _responseEditor!.Text = e.Text;
|
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||||
|
SukiTheme.GetInstance().OnBaseThemeChanged -= OnBaseThemeChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||||
@@ -49,8 +73,11 @@ public partial class ConsoleView : UserControl
|
|||||||
var registryOptions = new RegistryOptions(
|
var registryOptions = new RegistryOptions(
|
||||||
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||||
|
|
||||||
var textMateInstallation = _responseEditor.InstallTextMate(registryOptions);
|
var responseTmi = _responseEditor.InstallTextMate(registryOptions);
|
||||||
textMateInstallation.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
responseTmi.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
||||||
|
.GetLanguageByExtension(".json").Id));
|
||||||
|
var requestTmi = _requestEditor.InstallTextMate(registryOptions);
|
||||||
|
requestTmi.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
||||||
.GetLanguageByExtension(".json").Id));
|
.GetLanguageByExtension(".json").Id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,313 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
xmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||||
|
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
|
||||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||||
|
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Desktop.Views.EndpointView"
|
x:Class="Needlework.Net.Desktop.Views.EndpointView"
|
||||||
x:DataType="vm:EndpointViewModel"
|
x:DataType="vm:EndpointViewModel">
|
||||||
Title="{Binding Title}"
|
<Grid RowDefinitions="auto,*" ColumnDefinitions="3*,2,4*,2,4*">
|
||||||
Width="1280"
|
<Grid Grid.Row="0"
|
||||||
Height="720">
|
Grid.Column="0"
|
||||||
<Grid Margin="8" RowDefinitions="auto,*" ColumnDefinitions="*">
|
Grid.RowSpan="2"
|
||||||
<TextBlock Classes="h3" Grid.Row="0" Grid.Column="0" Text="{Binding Endpoint}"/>
|
RowDefinitions="auto,*"
|
||||||
<ScrollViewer Grid.Row="1" Grid.Column="0">
|
ColumnDefinitions="*">
|
||||||
|
<ListBox ItemsSource="{Binding PathOperations}"
|
||||||
</ScrollViewer>
|
SelectedItem="{Binding SelectedPathOperation}"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Visible"
|
||||||
|
Margin="0 0 0 0"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
Grid.Column="0">
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid
|
||||||
|
RowDefinitions="*"
|
||||||
|
ColumnDefinitions="auto,*">
|
||||||
|
<Button
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Classes="Flat"
|
||||||
|
Margin="0 0 8 0"
|
||||||
|
Content="{Binding Method}"
|
||||||
|
Background="{Binding Color}"
|
||||||
|
FontSize="8"
|
||||||
|
Width="45"
|
||||||
|
Padding="10 2 10 2"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"/>
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{Binding Path}"
|
||||||
|
FontSize="11"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</Grid>
|
||||||
|
<GridSplitter Background="Gray"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.RowSpan="2"
|
||||||
|
Grid.Column="1"/>
|
||||||
|
<Grid Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
RowDefinitions="*"
|
||||||
|
ColumnDefinitions="auto,*,auto">
|
||||||
|
<TextBox Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Text="{Binding SelectedPathOperation.Method}"
|
||||||
|
FontSize="12"
|
||||||
|
IsReadOnly="True"/>
|
||||||
|
<TextBox Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
FontSize="12"
|
||||||
|
Text="{Binding SelectedPathOperation.ResponsePath}"
|
||||||
|
IsReadOnly="True"/>
|
||||||
|
<StackPanel Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<Button Classes="Flat"
|
||||||
|
Margin="4"
|
||||||
|
FontSize="12"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Padding="12 4 12 4"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Command="{Binding SelectedPathOperation.SendRequestCommand}">Send</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<Grid Grid.Row="1" Grid.Column="2">
|
||||||
|
<TabControl>
|
||||||
|
<TabItem Header="Params">
|
||||||
|
<ScrollViewer>
|
||||||
|
<StackPanel IsVisible="{Binding SelectedPathOperation, Converter={StaticResource NullBoolConverter}}">
|
||||||
|
<suki:GroupBox Header="Path Parameters"
|
||||||
|
Margin="0 4"
|
||||||
|
IsVisible="{Binding SelectedPathOperation.Operation.PathParameters, Converter={StaticResource EnumerableBoolConverter}}">
|
||||||
|
<DataGrid
|
||||||
|
ItemsSource="{Binding SelectedPathOperation.Operation.PathParameters}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal">
|
||||||
|
<DataGrid.Styles>
|
||||||
|
<Style Selector="DataGridRow DataGridCell">
|
||||||
|
<Setter Property="FontSize" Value="12"></Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Styles>
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
|
||||||
|
<DataGridCheckBoxColumn Header="Required" Binding="{Binding IsRequired}"/>
|
||||||
|
<DataGridTemplateColumn Header="Value">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate DataType="vm:ParameterViewModel">
|
||||||
|
<TextBox Text="{Binding Value}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</suki:GroupBox>
|
||||||
|
<suki:GroupBox Header="Query Parameters"
|
||||||
|
Margin="0 4"
|
||||||
|
IsVisible="{Binding SelectedPathOperation.Operation.QueryParameters, Converter={StaticResource EnumerableBoolConverter}}">
|
||||||
|
<DataGrid
|
||||||
|
ItemsSource="{Binding SelectedPathOperation.Operation.QueryParameters}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal">
|
||||||
|
<DataGrid.Styles>
|
||||||
|
<Style Selector="DataGridRow DataGridCell">
|
||||||
|
<Setter Property="FontSize" Value="12"></Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Styles>
|
||||||
|
<DataGrid.Columns>
|
||||||
|
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
|
||||||
|
<DataGridCheckBoxColumn Header="Required" Binding="{Binding IsRequired}"/>
|
||||||
|
<DataGridTemplateColumn Header="Value">
|
||||||
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
|
<DataTemplate DataType="vm:ParameterViewModel">
|
||||||
|
<TextBox Text="{Binding Value}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
|
</DataGridTemplateColumn>
|
||||||
|
<DataGridTextColumn Header="Type" Binding="{Binding Type}"/>
|
||||||
|
</DataGrid.Columns>
|
||||||
|
</DataGrid>
|
||||||
|
</suki:GroupBox>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="Body">
|
||||||
|
<avalonEdit:TextEditor
|
||||||
|
Name="EndpointRequestEditor"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollBarVisibility="Visible"
|
||||||
|
Text=""
|
||||||
|
ShowLineNumbers="True"
|
||||||
|
FontSize="12"/>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="Auth">
|
||||||
|
<Grid RowDefinitions="auto,auto,auto,*" ColumnDefinitions="*,4*">
|
||||||
|
<TextBlock FontSize="12"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
Username
|
||||||
|
</TextBlock>
|
||||||
|
<TextBox FontSize="12"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Text="{Binding SelectedPathOperation.ResponseUsername}" />
|
||||||
|
<TextBlock FontSize="12"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
Password
|
||||||
|
</TextBlock>
|
||||||
|
<TextBox FontSize="12"
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="1"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Text="{Binding SelectedPathOperation.ResponsePassword}"/>
|
||||||
|
<TextBlock FontSize="12"
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
Authorization
|
||||||
|
</TextBlock>
|
||||||
|
<TextBox FontSize="12"
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="1"
|
||||||
|
IsReadOnly="True"
|
||||||
|
Text="{Binding SelectedPathOperation.ResponseAuthorization}"/>
|
||||||
|
</Grid>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem Header="Schemas">
|
||||||
|
<ScrollViewer>
|
||||||
|
<StackPanel>
|
||||||
|
<suki:GlassCard Margin="0 4" IsVisible="{Binding SelectedPathOperation.Operation.RequestBodyType, Converter={StaticResource NullBoolConverter}}">
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="Request body: " FontWeight="DemiBold" FontSize="12"/>
|
||||||
|
<Run Text="{Binding SelectedPathOperation.Operation.RequestBodyType}" FontSize="12"/>
|
||||||
|
</TextBlock>
|
||||||
|
</suki:GlassCard>
|
||||||
|
<Border Margin="0 4" IsVisible="{Binding SelectedPathOperation.Operation.RequestClasses, Converter={StaticResource EnumerableBoolConverter}}">
|
||||||
|
<ItemsRepeater ItemsSource="{Binding SelectedPathOperation.Operation.RequestClasses}">
|
||||||
|
<ItemsRepeater.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Margin="0 4 0 8">
|
||||||
|
<suki:GlassCard IsVisible="{Binding PropertyFields, Converter={StaticResource EnumerableBoolConverter}}">
|
||||||
|
<suki:GroupBox Header="{Binding Id}">
|
||||||
|
<DataGrid
|
||||||
|
ItemsSource="{Binding PropertyFields}"
|
||||||
|
AutoGenerateColumns="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal">
|
||||||
|
<DataGrid.Styles>
|
||||||
|
<Style Selector="DataGridRow DataGridCell">
|
||||||
|
<Setter Property="FontSize" Value="12"></Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Styles>
|
||||||
|
</DataGrid>
|
||||||
|
</suki:GroupBox>
|
||||||
|
</suki:GlassCard>
|
||||||
|
<suki:GlassCard Margin="0 0 0 8" IsVisible="{Binding PropertyEnums, Converter={StaticResource EnumerableBoolConverter}}">
|
||||||
|
<suki:GroupBox Header="{Binding Id}">
|
||||||
|
<DataGrid
|
||||||
|
ItemsSource="{Binding PropertyEnums}"
|
||||||
|
AutoGenerateColumns="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal">
|
||||||
|
<DataGrid.Styles>
|
||||||
|
<Style Selector="DataGridRow DataGridCell">
|
||||||
|
<Setter Property="FontSize" Value="12"></Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Styles>
|
||||||
|
</DataGrid>
|
||||||
|
</suki:GroupBox>
|
||||||
|
</suki:GlassCard>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsRepeater.ItemTemplate>
|
||||||
|
</ItemsRepeater>
|
||||||
|
</Border>
|
||||||
|
<suki:GlassCard Margin="0 4">
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="Return value: " FontWeight="DemiBold" FontSize="12"/>
|
||||||
|
<Run Text="{Binding SelectedPathOperation.Operation.ReturnType}" FontSize="12"/>
|
||||||
|
</TextBlock>
|
||||||
|
</suki:GlassCard>
|
||||||
|
<Border Margin="0 4" IsVisible="{Binding SelectedPathOperation.Operation.ResponseClasses, Converter={StaticResource EnumerableBoolConverter}}">
|
||||||
|
<ItemsRepeater ItemsSource="{Binding SelectedPathOperation.Operation.ResponseClasses}">
|
||||||
|
<ItemsRepeater.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<StackPanel Margin="0 4 0 8">
|
||||||
|
<suki:GlassCard IsVisible="{Binding PropertyFields, Converter={StaticResource EnumerableBoolConverter}}">
|
||||||
|
<suki:GroupBox Header="{Binding Id}">
|
||||||
|
<DataGrid
|
||||||
|
ItemsSource="{Binding PropertyFields}"
|
||||||
|
AutoGenerateColumns="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal">
|
||||||
|
<DataGrid.Styles>
|
||||||
|
<Style Selector="DataGridRow DataGridCell">
|
||||||
|
<Setter Property="FontSize" Value="12"></Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Styles>
|
||||||
|
</DataGrid>
|
||||||
|
</suki:GroupBox>
|
||||||
|
</suki:GlassCard>
|
||||||
|
<suki:GlassCard Margin="0 0 0 8" IsVisible="{Binding PropertyEnums, Converter={StaticResource EnumerableBoolConverter}}">
|
||||||
|
<suki:GroupBox Header="{Binding Id}">
|
||||||
|
<DataGrid
|
||||||
|
ItemsSource="{Binding PropertyEnums}"
|
||||||
|
AutoGenerateColumns="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal">
|
||||||
|
<DataGrid.Styles>
|
||||||
|
<Style Selector="DataGridRow DataGridCell">
|
||||||
|
<Setter Property="FontSize" Value="12"></Setter>
|
||||||
|
</Style>
|
||||||
|
</DataGrid.Styles>
|
||||||
|
</DataGrid>
|
||||||
|
</suki:GroupBox>
|
||||||
|
</suki:GlassCard>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsRepeater.ItemTemplate>
|
||||||
|
</ItemsRepeater>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
</Grid>
|
||||||
|
<GridSplitter Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Background="Gray"/>
|
||||||
|
<StackPanel Grid.Row="0" Grid.Column="4" Orientation="Horizontal">
|
||||||
|
<Button HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="4"
|
||||||
|
FontSize="10"
|
||||||
|
Padding="12 4 12 4"
|
||||||
|
Classes="Flat"
|
||||||
|
Content="{Binding SelectedPathOperation.ResponseStatus}"/>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
<Grid Grid.Row="1" Grid.Column="4">
|
||||||
|
<TabControl>
|
||||||
|
<TabItem Header="Preview">
|
||||||
|
<avalonEdit:TextEditor
|
||||||
|
Name="EndpointResponseEditor"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollBarVisibility="Visible"
|
||||||
|
ShowLineNumbers="True"
|
||||||
|
Text=""
|
||||||
|
FontSize="12"/>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
|
|||||||
@@ -1,11 +1,83 @@
|
|||||||
using SukiUI.Controls;
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using AvaloniaEdit;
|
||||||
|
using AvaloniaEdit.TextMate;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Desktop.Extensions;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using Needlework.Net.Desktop.ViewModels;
|
||||||
|
using SukiUI;
|
||||||
|
using TextMateSharp.Grammars;
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop.Views;
|
namespace Needlework.Net.Desktop.Views;
|
||||||
|
|
||||||
public partial class EndpointView : SukiWindow
|
public partial class EndpointView : UserControl, IRecipient<EditorUpdateMessage>, IRecipient<ContentRequestMessage>
|
||||||
{
|
{
|
||||||
|
private TextEditor? _requestEditor;
|
||||||
|
private TextEditor? _responseEditor;
|
||||||
|
|
||||||
public EndpointView()
|
public EndpointView()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate(e);
|
||||||
|
|
||||||
|
var vm = (EndpointViewModel)DataContext!;
|
||||||
|
_requestEditor = this.FindControl<TextEditor>("EndpointRequestEditor");
|
||||||
|
_responseEditor = this.FindControl<TextEditor>("EndpointResponseEditor");
|
||||||
|
_requestEditor?.ApplyJsonEditorSettings();
|
||||||
|
_responseEditor?.ApplyJsonEditorSettings();
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Register<EditorUpdateMessage>(this);
|
||||||
|
WeakReferenceMessenger.Default.Register<ContentRequestMessage, string>(this, "EndpointRequestEditor");
|
||||||
|
|
||||||
|
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||||
|
SukiTheme.GetInstance().OnBaseThemeChanged += OnBaseThemeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnDetachedFromVisualTree(e);
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.UnregisterAll(this);
|
||||||
|
SukiTheme.GetInstance().OnBaseThemeChanged -= OnBaseThemeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||||
|
{
|
||||||
|
var registryOptions = new RegistryOptions(
|
||||||
|
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||||
|
|
||||||
|
var requestTmi = _requestEditor.InstallTextMate(registryOptions);
|
||||||
|
requestTmi.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
||||||
|
.GetLanguageByExtension(".json").Id));
|
||||||
|
var responseTmi = _requestEditor.InstallTextMate(registryOptions);
|
||||||
|
responseTmi.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
||||||
|
.GetLanguageByExtension(".json").Id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(EditorUpdateMessage message)
|
||||||
|
{
|
||||||
|
switch (message.Value.Key)
|
||||||
|
{
|
||||||
|
case "EndpointRequestEditor":
|
||||||
|
_requestEditor!.Text = message.Value.Text;
|
||||||
|
break;
|
||||||
|
case "EndpointResponseEditor":
|
||||||
|
_responseEditor!.Text = message.Value.Text;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(ContentRequestMessage message)
|
||||||
|
{
|
||||||
|
message.Reply(_requestEditor!.Text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
14
Needlework.Net.Desktop/Views/EndpointsContainerView.axaml
Normal file
14
Needlework.Net.Desktop/Views/EndpointsContainerView.axaml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<UserControl 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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||||
|
xmlns:theme="clr-namespace:SukiUI.Theme;assembly=SukiUI"
|
||||||
|
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||||
|
xmlns:avalonEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Needlework.Net.Desktop.Views.EndpointsContainerView"
|
||||||
|
x:DataType="vm:EndpointsContainerViewModel">
|
||||||
|
<suki:SukiStackPage Content="{Binding ActiveViewModel}"
|
||||||
|
Margin="-24 -4 0 0"/>
|
||||||
|
</UserControl>
|
||||||
11
Needlework.Net.Desktop/Views/EndpointsContainerView.axaml.cs
Normal file
11
Needlework.Net.Desktop/Views/EndpointsContainerView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Views;
|
||||||
|
|
||||||
|
public partial class EndpointsContainerView : UserControl
|
||||||
|
{
|
||||||
|
public EndpointsContainerView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,10 +8,8 @@
|
|||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
x:Class="Needlework.Net.Desktop.Views.EndpointsView"
|
x:Class="Needlework.Net.Desktop.Views.EndpointsView"
|
||||||
x:DataType="vm:EndpointsViewModel">
|
x:DataType="vm:EndpointsViewModel">
|
||||||
<!-- TOP LEVEL -->
|
|
||||||
<suki:BusyArea IsBusy="{Binding IsBusy}" BusyText="Loading...">
|
<suki:BusyArea IsBusy="{Binding IsBusy}" BusyText="Loading...">
|
||||||
<Grid Margin="16" RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
<Grid Margin="16" RowDefinitions="auto,auto,*" ColumnDefinitions="*">
|
||||||
<TextBlock Classes="h3" Margin="0 4" Grid.Row="0" Grid.Column="0">Endpoints</TextBlock>
|
|
||||||
<TextBox Watermark="Search" Margin="0 4" Text="{Binding Search}" Grid.Row="1" Grid.Column="0"/>
|
<TextBox Watermark="Search" Margin="0 4" Text="{Binding Search}" Grid.Row="1" Grid.Column="0"/>
|
||||||
<ScrollViewer Grid.Row="2" Grid.Column="0">
|
<ScrollViewer Grid.Row="2" Grid.Column="0">
|
||||||
<ListBox ItemsSource="{Binding Query}" SelectedItem="{Binding SelectedQuery}">
|
<ListBox ItemsSource="{Binding Query}" SelectedItem="{Binding SelectedQuery}">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<Window xmlns="https://github.com/avaloniaui"
|
<suki:SukiWindow
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
@@ -10,8 +11,15 @@
|
|||||||
x:Class="Needlework.Net.Desktop.Views.MainWindow"
|
x:Class="Needlework.Net.Desktop.Views.MainWindow"
|
||||||
x:DataType="vm:MainWindowViewModel"
|
x:DataType="vm:MainWindowViewModel"
|
||||||
Title="Needlework.Net"
|
Title="Needlework.Net"
|
||||||
|
Icon="/Assets/app.ico"
|
||||||
Width="1280"
|
Width="1280"
|
||||||
Height="720">
|
Height="720">
|
||||||
|
<suki:SukiWindow.LogoContent>
|
||||||
|
<Image Source="/Assets/app.png"
|
||||||
|
Width="20"
|
||||||
|
Height="20"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</suki:SukiWindow.LogoContent>
|
||||||
<!-- TOP LEVEL -->
|
<!-- TOP LEVEL -->
|
||||||
<suki:SukiSideMenu ItemsSource="{Binding Pages}">
|
<suki:SukiSideMenu ItemsSource="{Binding Pages}">
|
||||||
<!-- ITEMS -->
|
<!-- ITEMS -->
|
||||||
@@ -40,13 +48,25 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</StackPanel.Styles>
|
</StackPanel.Styles>
|
||||||
<Button Classes="Flat"
|
<Button Classes="Flat"
|
||||||
Content="{Binding Version}" />
|
Content="{Binding Version}"
|
||||||
|
FontSize="12"
|
||||||
|
Margin="0 0 4 0"
|
||||||
|
Padding="12 4 12 4"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
<Button Classes="Basic"
|
<Button Classes="Basic"
|
||||||
|
VerticalAlignment="Center"
|
||||||
CommandParameter="https://github.com/BlossomiShymae/Needlework.Net"
|
CommandParameter="https://github.com/BlossomiShymae/Needlework.Net"
|
||||||
ToolTip.Tip="Open on GitHub.">
|
ToolTip.Tip="Open on GitHub."
|
||||||
<materialIcons:MaterialIcon Kind="Github" />
|
Margin="0 0 4 0">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<materialIcons:MaterialIcon Kind="Github" Margin="0 0 4 0" />
|
||||||
|
<TextBlock FontSize="12"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="White">Star</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
</Button>
|
</Button>
|
||||||
<Button Classes="Basic"
|
<Button Classes="Basic"
|
||||||
|
VerticalAlignment="Center"
|
||||||
CommandParameter="https://discord.gg/chEvEX5J4E"
|
CommandParameter="https://discord.gg/chEvEX5J4E"
|
||||||
ToolTip.Tip="Open Discord server.">
|
ToolTip.Tip="Open Discord server.">
|
||||||
<i:Icon Value="fa-brand fa-discord" />
|
<i:Icon Value="fa-brand fa-discord" />
|
||||||
@@ -54,4 +74,4 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</suki:SukiSideMenu.FooterContent>
|
</suki:SukiSideMenu.FooterContent>
|
||||||
</suki:SukiSideMenu>
|
</suki:SukiSideMenu>
|
||||||
</Window>
|
</suki:SukiWindow>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
using SukiUI.Controls;
|
using SukiUI.Controls;
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop.Views;
|
namespace Needlework.Net.Desktop.Views;
|
||||||
|
|||||||
45
Needlework.Net.Desktop/Views/OopsiesWindow.axaml
Normal file
45
Needlework.Net.Desktop/Views/OopsiesWindow.axaml
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<suki:SukiWindow
|
||||||
|
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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||||
|
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Needlework.Net.Desktop.Views.OopsiesWindow"
|
||||||
|
x:DataType="vm:OopsiesWindowViewModel"
|
||||||
|
Title="Needlework.Net - Oopsies"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
Width="560"
|
||||||
|
Height="200">
|
||||||
|
<Grid RowDefinitions="auto,auto,auto" ColumnDefinitions="auto,auto"
|
||||||
|
Margin="8"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2">
|
||||||
|
This response is too large for Needlework.Net to handle for performance reasons.
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Grid.ColumnSpan="2"
|
||||||
|
Margin="0 0 0 12">
|
||||||
|
It can be viewed in an external editor or viewer.
|
||||||
|
</TextBlock>
|
||||||
|
<Button Command="{Binding OpenDefaultEditorCommand}"
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="0"
|
||||||
|
Margin="0 0 8 0">
|
||||||
|
Open
|
||||||
|
</Button>
|
||||||
|
<Button Command="{Binding CloseDialogCommand}"
|
||||||
|
Grid.Row="2"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="8 0 0 0">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</suki:SukiWindow>
|
||||||
11
Needlework.Net.Desktop/Views/OopsiesWindow.axaml.cs
Normal file
11
Needlework.Net.Desktop/Views/OopsiesWindow.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using SukiUI.Controls;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Views;
|
||||||
|
|
||||||
|
public partial class OopsiesWindow : SukiWindow
|
||||||
|
{
|
||||||
|
public OopsiesWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
<UserControl 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:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
|
||||||
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
|
||||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
|
||||||
x:Class="Needlework.Net.Desktop.Views.PluginView"
|
|
||||||
x:DataType="vm:PluginViewModel">
|
|
||||||
<!-- TOP LEVEL -->
|
|
||||||
<ScrollViewer>
|
|
||||||
<StackPanel Margin="8">
|
|
||||||
<suki:GlassCard>
|
|
||||||
<StackPanel>
|
|
||||||
|
|
||||||
</StackPanel>
|
|
||||||
</suki:GlassCard>
|
|
||||||
</StackPanel>
|
|
||||||
</ScrollViewer>
|
|
||||||
</UserControl>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using Avalonia.Controls;
|
|
||||||
|
|
||||||
namespace Needlework.Net.Desktop.Views
|
|
||||||
{
|
|
||||||
public partial class PluginView : UserControl
|
|
||||||
{
|
|
||||||
public PluginView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
68
Needlework.Net.Desktop/Views/WebsocketView.axaml
Normal file
68
Needlework.Net.Desktop/Views/WebsocketView.axaml
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<UserControl 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:avaloniaEdit="https://github.com/avaloniaui/avaloniaedit"
|
||||||
|
xmlns:suki="clr-namespace:SukiUI.Controls;assembly=SukiUI"
|
||||||
|
xmlns:vm="using:Needlework.Net.Desktop.ViewModels"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Needlework.Net.Desktop.Views.WebsocketView"
|
||||||
|
x:DataType="vm:WebsocketViewModel">
|
||||||
|
<Grid RowDefinitions="*,2,*" Margin="16">
|
||||||
|
<Border Grid.Row="0"
|
||||||
|
Padding="0 0 0 8">
|
||||||
|
<suki:GlassCard>
|
||||||
|
<Grid RowDefinitions="auto,*" ColumnDefinitions="*">
|
||||||
|
<Grid
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
RowDefinitions="*"
|
||||||
|
ColumnDefinitions="auto,*,auto,auto">
|
||||||
|
<Button Grid.Row="0"
|
||||||
|
Grid.Column="0"
|
||||||
|
Command="{Binding ClearCommand}"
|
||||||
|
Margin="0 0 8 0">Clear</Button>
|
||||||
|
<TextBox Grid.Row="0"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0 0 8 0"
|
||||||
|
Text="{Binding Search, Mode=TwoWay}"/>
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="2"
|
||||||
|
Margin="0 0 8 0">
|
||||||
|
<ToggleSwitch Margin="0 0 0 8"
|
||||||
|
IsChecked="{Binding IsAttach}"/>
|
||||||
|
<TextBlock Margin="0 6 0 0"
|
||||||
|
FontSize="18">Attach</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Grid.Row="0"
|
||||||
|
Grid.Column="3">
|
||||||
|
<ToggleSwitch Margin="0 0 0 8"
|
||||||
|
IsChecked="{Binding IsTail}"/>
|
||||||
|
<TextBlock Margin="0 6 0 0"
|
||||||
|
FontSize="18">Tail</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<ListBox Grid.Row="1"
|
||||||
|
Grid.Column="0"
|
||||||
|
Name="EventViewer"
|
||||||
|
ItemsSource="{Binding FilteredEventLog}"
|
||||||
|
SelectedItem="{Binding SelectedEventLog}"/>
|
||||||
|
</Grid>
|
||||||
|
</suki:GlassCard>
|
||||||
|
</Border>
|
||||||
|
<GridSplitter Grid.Row="1" ResizeDirection="Rows" Background="Gray"/>
|
||||||
|
<Border Grid.Row="2"
|
||||||
|
Padding="0 8 0 0">
|
||||||
|
<suki:GlassCard>
|
||||||
|
<avaloniaEdit:TextEditor
|
||||||
|
Name="ResponseEditor"
|
||||||
|
HorizontalScrollBarVisibility="Auto"
|
||||||
|
VerticalScrollBarVisibility="Visible"
|
||||||
|
Text=""
|
||||||
|
FontSize="14"/>
|
||||||
|
</suki:GlassCard>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
57
Needlework.Net.Desktop/Views/WebsocketView.axaml.cs
Normal file
57
Needlework.Net.Desktop/Views/WebsocketView.axaml.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using AvaloniaEdit;
|
||||||
|
using AvaloniaEdit.TextMate;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Needlework.Net.Desktop.Extensions;
|
||||||
|
using Needlework.Net.Desktop.Messages;
|
||||||
|
using Needlework.Net.Desktop.ViewModels;
|
||||||
|
using SukiUI;
|
||||||
|
using TextMateSharp.Grammars;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Desktop.Views;
|
||||||
|
|
||||||
|
public partial class WebsocketView : UserControl, IRecipient<ResponseUpdatedMessage>
|
||||||
|
{
|
||||||
|
private TextEditor? _responseEditor;
|
||||||
|
|
||||||
|
public WebsocketView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Receive(ResponseUpdatedMessage message)
|
||||||
|
{
|
||||||
|
_responseEditor!.Text = message.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnApplyTemplate(e);
|
||||||
|
|
||||||
|
var vm = (WebsocketViewModel)DataContext!;
|
||||||
|
var viewer = this.FindControl<ListBox>("EventViewer");
|
||||||
|
viewer!.PropertyChanged += (s, e) => { if (vm.IsTail) viewer.ScrollIntoView(vm.EventLog.Count - 1); };
|
||||||
|
|
||||||
|
_responseEditor = this.FindControl<TextEditor>("ResponseEditor");
|
||||||
|
_responseEditor?.ApplyJsonEditorSettings();
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Register(this, nameof(WebsocketViewModel));
|
||||||
|
|
||||||
|
OnBaseThemeChanged(Application.Current!.ActualThemeVariant);
|
||||||
|
SukiTheme.GetInstance().OnBaseThemeChanged += OnBaseThemeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnBaseThemeChanged(ThemeVariant currentTheme)
|
||||||
|
{
|
||||||
|
|
||||||
|
var registryOptions = new RegistryOptions(
|
||||||
|
currentTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus);
|
||||||
|
|
||||||
|
var responseTmi = _responseEditor.InstallTextMate(registryOptions);
|
||||||
|
responseTmi.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions
|
||||||
|
.GetLanguageByExtension(".json").Id));
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Needlework.Net.Desktop/app.ico
Normal file
BIN
Needlework.Net.Desktop/app.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
38
README.md
Normal file
38
README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Needlework.Net
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Needlework.Net is an open-source helper tool for the LCU that provides documented endpoints and can send requests without any code setup. Created using .NET! 🌠
|
||||||
|
|
||||||
|
## Download
|
||||||
|
|
||||||
|
[Needlework can be downloaded from the latest release for Windows!](https://github.com/BlossomiShymae/Needlework/releases)
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
<a href="https://github.com/BlossomiShymae/Needlework.Net/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=BlossomiShymae/Needlework.Net" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
### LCU Explorer
|
||||||
|
|
||||||
|
This project was inspired by LCU Explorer, an application created by the HextechDocs team! 💚
|
||||||
|
- [Repository](https://github.com/HextechDocs/lcu-explorer)
|
||||||
|
|
||||||
|
### hasagi-types
|
||||||
|
|
||||||
|
Endpoints and schemas are provided by dysolix's [generated OpenAPI file.](https://raw.githubusercontent.com/dysolix/hasagi-types/main/swagger.json) Thank you!
|
||||||
|
- [Repository](https://github.com/dysolix/hasagi-types)
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
|
THE PROGRAM IS PROVIDED “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF MERCHANTABILITY, NONINFRINGMENT, OR OF FITNESS FOR A PARTICULAR PURPOSE. LICENSOR DOES NOT WARRANT THAT THE FUNCTIONS CONTAINED IN THE PROGRAM WILL MEET YOUR REQUIREMENTS OR THAT OPERATION WILL BE UNINTERRUPTED OR ERROR FREE. LICENSOR MAKES NO WARRANTIES RESPECTING ANY HARM THAT MAY BE CAUSED BY MALICIOUS USE OF THIS SOFTWARE. LICENSOR FURTHER EXPRESSLY DISCLAIMS ANY WARRANTY OR REPRESENTATION TO AUTHORIZED USERS OR TO ANY THIRD PARTY.
|
||||||
|
|
||||||
|
|
||||||
|
Needlework isn't endorsed by Riot Games and doesn't
|
||||||
|
reflect the views or opinions of Riot Games or anyone officially
|
||||||
|
involved in producing or managing Riot Games properties. Riot Games,
|
||||||
|
and all associated properties are trademarks or registered
|
||||||
|
trademarks of Riot Games, Inc.
|
||||||
BIN
app-preview.png
Normal file
BIN
app-preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 246 KiB |
Reference in New Issue
Block a user