mirror of
https://github.com/BlossomiShymae/Needlework.Net.git
synced 2025-12-06 10:10:48 +01:00
feat: add schema search pane
This commit is contained in:
243
Needlework.Net/Helpers/OpenApiHelpers.cs
Normal file
243
Needlework.Net/Helpers/OpenApiHelpers.cs
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Needlework.Net.Models;
|
||||||
|
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Helpers
|
||||||
|
{
|
||||||
|
public static class OpenApiHelpers
|
||||||
|
{
|
||||||
|
public static string GetReturnType(OpenApiResponses responses)
|
||||||
|
{
|
||||||
|
if (!TryGetResponse(responses, out var response))
|
||||||
|
return "none";
|
||||||
|
|
||||||
|
if (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 == null) return "object"; // Because GetLolVanguardV1Notification exists where it has a required parameter without a type...
|
||||||
|
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.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<string> CreateTemplate(List<PropertyClassViewModel> requestClasses)
|
||||||
|
{
|
||||||
|
if (requestClasses.Count == 0) return [];
|
||||||
|
List<string> template = [];
|
||||||
|
template.Add("{");
|
||||||
|
|
||||||
|
var rootClass = requestClasses.First();
|
||||||
|
if (rootClass.PropertyEnums.Any()) return [rootClass.PropertyEnums.First().Values];
|
||||||
|
var propertyFields = rootClass.PropertyFields;
|
||||||
|
for (int i = 0; i < propertyFields.Count; i++)
|
||||||
|
{
|
||||||
|
template.Add($"\"{propertyFields[i].Name}\"");
|
||||||
|
template.Add(":");
|
||||||
|
template.Add($"#{propertyFields[i].Type}");
|
||||||
|
|
||||||
|
if (i == propertyFields.Count - 1) template.Add("}");
|
||||||
|
else template.Add(",");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < template.Count; i++)
|
||||||
|
{
|
||||||
|
var type = template[i];
|
||||||
|
if (!type.Contains("#")) continue;
|
||||||
|
|
||||||
|
var foundClass = requestClasses.Where(c => c.Id == type.Replace("#", string.Empty));
|
||||||
|
if (foundClass.Any())
|
||||||
|
{
|
||||||
|
if (foundClass.First().PropertyEnums.Any())
|
||||||
|
{
|
||||||
|
template[i] = string.Join(string.Empty, CreateTemplate([.. foundClass]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
List<PropertyClassViewModel> classes = [.. requestClasses];
|
||||||
|
classes.Remove(rootClass);
|
||||||
|
template[i] = string.Join(string.Empty, CreateTemplate(classes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
template[i] = GetRequestDefaultValue(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
public 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ParameterViewModel> GetParameters(List<OpenApiParameter> parameters, ParameterLocation location)
|
||||||
|
{
|
||||||
|
var pathParameters = new List<ParameterViewModel>();
|
||||||
|
foreach (var parameter in parameters)
|
||||||
|
{
|
||||||
|
if (parameter.In != location) continue;
|
||||||
|
pathParameters.Add(new ParameterViewModel(parameter.Name, GetSchemaType(parameter.Schema), parameter.Required));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? GetRequestBodyType(OpenApiRequestBody? requestBody)
|
||||||
|
{
|
||||||
|
if (requestBody == null) return null;
|
||||||
|
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||||
|
{
|
||||||
|
var schema = media.Schema;
|
||||||
|
if (schema == null) return null; // Because "PostLolAccountVerificationV1SendDeactivationPin" exists where the media body is empty...
|
||||||
|
return GetSchemaType(schema);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document document)
|
||||||
|
{
|
||||||
|
if (requestBody == null) return [];
|
||||||
|
if (requestBody.Content.TryGetValue("application/json", out var media))
|
||||||
|
{
|
||||||
|
var rawDocument = document.OpenApiDocument;
|
||||||
|
var schema = media.Schema;
|
||||||
|
if (schema == null) return [];
|
||||||
|
|
||||||
|
var type = GetSchemaType(media.Schema);
|
||||||
|
if (IsComponent(type))
|
||||||
|
{
|
||||||
|
var componentId = GetComponentId(schema);
|
||||||
|
var componentSchema = rawDocument.Components.Schemas[componentId];
|
||||||
|
List<PropertyClassViewModel> propertyClasses = [];
|
||||||
|
WalkSchema(componentSchema, propertyClasses, rawDocument);
|
||||||
|
return propertyClasses;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetRequestDefaultValue(string type)
|
||||||
|
{
|
||||||
|
var defaultValue = string.Empty;
|
||||||
|
if (type.Contains("[]")) defaultValue = "[]";
|
||||||
|
else if (type.Contains("string")) defaultValue = "\"\"";
|
||||||
|
else if (type.Contains("boolean")) defaultValue = "false";
|
||||||
|
else if (type.Contains("integer")) defaultValue = "0";
|
||||||
|
else if (type.Contains("double") || type.Contains("float")) defaultValue = "0.0";
|
||||||
|
else if (type.Contains("object")) defaultValue = "{}";
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document document)
|
||||||
|
{
|
||||||
|
var requestClasses = GetRequestClasses(requestBody, document);
|
||||||
|
if (requestClasses.Count == 0)
|
||||||
|
{
|
||||||
|
var type = GetRequestBodyType(requestBody);
|
||||||
|
if (type == null) return null;
|
||||||
|
return GetRequestDefaultValue(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
var template = CreateTemplate(requestClasses);
|
||||||
|
return JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(string.Join(string.Empty, template)), App.JsonSerializerOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document document)
|
||||||
|
{
|
||||||
|
if (!TryGetResponse(responses, out var response))
|
||||||
|
return [];
|
||||||
|
|
||||||
|
if (response.Content.TryGetValue("application/json", out var media))
|
||||||
|
{
|
||||||
|
var rawDocument = document.OpenApiDocument;
|
||||||
|
var schema = media.Schema;
|
||||||
|
if (schema == null) return [];
|
||||||
|
|
||||||
|
List<PropertyClassViewModel> propertyClasses = [];
|
||||||
|
WalkSchema(schema, propertyClasses, rawDocument);
|
||||||
|
return propertyClasses;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsComponent(string type)
|
||||||
|
{
|
||||||
|
return !(type.Contains("object")
|
||||||
|
|| type.Contains("array")
|
||||||
|
|| type.Contains("bool")
|
||||||
|
|| type.Contains("string")
|
||||||
|
|| type.Contains("integer")
|
||||||
|
|| type.Contains("number"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetResponse(OpenApiResponses responses, [NotNullWhen(true)] out OpenApiResponse? response)
|
||||||
|
{
|
||||||
|
response = null;
|
||||||
|
var flag = false;
|
||||||
|
if (responses.TryGetValue("2XX", out var x))
|
||||||
|
{
|
||||||
|
response = x;
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
else if (responses.TryGetValue("200", out var y))
|
||||||
|
{
|
||||||
|
response = y;
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
return flag;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void WalkSchema(OpenApiSchema schema, List<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);
|
||||||
|
|
||||||
|
if (propertyClasses.Where(c => c.Id == componentId).Any()) return; // Avoid adding duplicate schemas in classes
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PropertyClassViewModel WalkSchema(OpenApiSchema schema, OpenApiDocument document)
|
||||||
|
{
|
||||||
|
string componentId = GetComponentId(schema);
|
||||||
|
var componentSchema = document.Components.Schemas[componentId];
|
||||||
|
var propertyClass = new PropertyClassViewModel(componentId, componentSchema.Properties, componentSchema.Enum);
|
||||||
|
return propertyClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,15 +17,15 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia" Version="11.3.1" />
|
<PackageReference Include="Avalonia" Version="11.2.8" />
|
||||||
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.3.0" />
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.3.1" />
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.8" />
|
||||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.1.5" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.3.1" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.2.8" />
|
||||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.3.1" />
|
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.8" />
|
||||||
<!--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.3.1" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.2.8" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.3.1" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.2.8" />
|
||||||
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.3.0" />
|
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.3.0" />
|
||||||
<PackageReference Include="BlossomiShymae.Briar" Version="0.2.0" />
|
<PackageReference Include="BlossomiShymae.Briar" Version="0.2.0" />
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Microsoft.OpenApi.Readers;
|
|||||||
using Needlework.Net.Extensions;
|
using Needlework.Net.Extensions;
|
||||||
using Needlework.Net.Models;
|
using Needlework.Net.Models;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Needlework.Net
|
namespace Needlework.Net
|
||||||
@@ -20,7 +21,7 @@ namespace Needlework.Net
|
|||||||
_githubUserContentClient = clients.Get("GithubUserContentClient");
|
_githubUserContentClient = clients.Get("GithubUserContentClient");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Document> GetLcuSchemaDocumentAsync()
|
public async Task<Document> GetLcuSchemaDocumentAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (Cached<Document>.TryGet(nameof(GetLcuSchemaDocumentAsync), out var cached))
|
if (Cached<Document>.TryGet(nameof(GetLcuSchemaDocumentAsync), out var cached))
|
||||||
{
|
{
|
||||||
@@ -28,14 +29,14 @@ namespace Needlework.Net
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lcuSchemaStream = await _githubUserContentClient.Request("/dysolix/hasagi-types/main/swagger.json")
|
var lcuSchemaStream = await _githubUserContentClient.Request("/dysolix/hasagi-types/main/swagger.json")
|
||||||
.GetStreamAsync();
|
.GetStreamAsync(cancellationToken: cancellationToken);
|
||||||
var lcuSchemaRaw = _reader.Read(lcuSchemaStream, out var _);
|
var lcuSchemaRaw = _reader.Read(lcuSchemaStream, out var _);
|
||||||
var document = new Document(lcuSchemaRaw);
|
var document = new Document(lcuSchemaRaw);
|
||||||
|
|
||||||
return cached.Save(document, TimeSpan.FromMinutes(60));
|
return cached.Save(document, TimeSpan.FromMinutes(60));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Document> GetLolClientDocumentAsync()
|
public async Task<Document> GetLolClientDocumentAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (Cached<Document>.TryGet(nameof(GetLolClientDocumentAsync), out var cached))
|
if (Cached<Document>.TryGet(nameof(GetLolClientDocumentAsync), out var cached))
|
||||||
{
|
{
|
||||||
@@ -43,7 +44,7 @@ namespace Needlework.Net
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lolClientStream = await _githubUserContentClient.Request("/AlsoSylv/Irelia/refs/heads/master/schemas/game_schema.json")
|
var lolClientStream = await _githubUserContentClient.Request("/AlsoSylv/Irelia/refs/heads/master/schemas/game_schema.json")
|
||||||
.GetStreamAsync();
|
.GetStreamAsync(cancellationToken: cancellationToken);
|
||||||
var lolClientRaw = _reader.Read(lolClientStream, out var _);
|
var lolClientRaw = _reader.Read(lolClientStream, out var _);
|
||||||
var document = new Document(lolClientRaw);
|
var document = new Document(lolClientRaw);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
|
using Avalonia.Threading;
|
||||||
using BlossomiShymae.Briar;
|
using BlossomiShymae.Briar;
|
||||||
using BlossomiShymae.Briar.Utils;
|
using BlossomiShymae.Briar.Utils;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
@@ -9,6 +10,7 @@ using FluentAvalonia.UI.Controls;
|
|||||||
using Flurl.Http;
|
using Flurl.Http;
|
||||||
using Flurl.Http.Configuration;
|
using Flurl.Http.Configuration;
|
||||||
using Needlework.Net.Extensions;
|
using Needlework.Net.Extensions;
|
||||||
|
using Needlework.Net.Helpers;
|
||||||
using Needlework.Net.Messages;
|
using Needlework.Net.Messages;
|
||||||
using Needlework.Net.Models;
|
using Needlework.Net.Models;
|
||||||
using Needlework.Net.Services;
|
using Needlework.Net.Services;
|
||||||
@@ -23,6 +25,7 @@ using System.Net.Http.Json;
|
|||||||
using System.Reactive;
|
using System.Reactive;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.MainWindow;
|
namespace Needlework.Net.ViewModels.MainWindow;
|
||||||
@@ -104,6 +107,15 @@ public partial class MainWindowViewModel
|
|||||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private bool _isPaneOpen;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private ObservableCollection<SchemaViewModel> _schemas = [];
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private SchemaViewModel? _selectedSchema;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private ObservableCollection<NotificationViewModel> _notifications = [];
|
private ObservableCollection<NotificationViewModel> _notifications = [];
|
||||||
|
|
||||||
@@ -113,6 +125,9 @@ public partial class MainWindowViewModel
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private PageBase _currentPage;
|
private PageBase _currentPage;
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
private SchemaSearchDetailsViewModel? _selectedSchemaSearchDetails;
|
||||||
|
|
||||||
public List<NavigationViewItem> NavigationViewItems { get; private set; } = [];
|
public List<NavigationViewItem> NavigationViewItems { get; private set; } = [];
|
||||||
|
|
||||||
public bool IsSchemaVersionChecked { get; private set; }
|
public bool IsSchemaVersionChecked { get; private set; }
|
||||||
@@ -121,6 +136,54 @@ public partial class MainWindowViewModel
|
|||||||
|
|
||||||
public string Title => $"Needlework.Net {Version}";
|
public string Title => $"Needlework.Net {Version}";
|
||||||
|
|
||||||
|
partial void OnSelectedNavigationViewItemChanged(NavigationViewItem value)
|
||||||
|
{
|
||||||
|
if (value.Tag is PageBase page)
|
||||||
|
{
|
||||||
|
CurrentPage = page;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedSchemaSearchDetailsChanged(SchemaSearchDetailsViewModel? value)
|
||||||
|
{
|
||||||
|
if (value == null) return;
|
||||||
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var document = value.Tab switch
|
||||||
|
{
|
||||||
|
Pages.Endpoints.Tab.LCU => await _documentService.GetLcuSchemaDocumentAsync(),
|
||||||
|
Pages.Endpoints.Tab.GameClient => await _documentService.GetLolClientDocumentAsync(),
|
||||||
|
_ => throw new NotImplementedException()
|
||||||
|
};
|
||||||
|
var propertyClassViewModel = OpenApiHelpers.WalkSchema(document.OpenApiDocument.Components.Schemas[value.Key], document.OpenApiDocument);
|
||||||
|
var schemaViewModel = new SchemaViewModel(propertyClassViewModel);
|
||||||
|
Dispatcher.UIThread.Post(() =>
|
||||||
|
{
|
||||||
|
if (Schemas.ToList().Find(schema => schema.Id == schemaViewModel.Id) == null)
|
||||||
|
{
|
||||||
|
Schemas.Add(schemaViewModel);
|
||||||
|
IsPaneOpen = true;
|
||||||
|
|
||||||
|
OpenSchemaPaneCommand.NotifyCanExecuteChanged();
|
||||||
|
CloseSchemaAllCommand.NotifyCanExecuteChanged();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSelectedSchemaChanged(SchemaViewModel? value)
|
||||||
|
{
|
||||||
|
CloseSchemaCommand.NotifyCanExecuteChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnSchemasChanged(ObservableCollection<SchemaViewModel> value)
|
||||||
|
{
|
||||||
|
if (!value.Any())
|
||||||
|
{
|
||||||
|
IsPaneOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private NavigationViewItem ToNavigationViewItem(PageBase page) => new()
|
private NavigationViewItem ToNavigationViewItem(PageBase page) => new()
|
||||||
{
|
{
|
||||||
Content = page.DisplayName,
|
Content = page.DisplayName,
|
||||||
@@ -140,14 +203,6 @@ public partial class MainWindowViewModel
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
partial void OnSelectedNavigationViewItemChanged(NavigationViewItem value)
|
|
||||||
{
|
|
||||||
if (value.Tag is PageBase page)
|
|
||||||
{
|
|
||||||
CurrentPage = page;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CheckForUpdatesAsync()
|
private async Task CheckForUpdatesAsync()
|
||||||
{
|
{
|
||||||
var release = await _githubClient
|
var release = await _githubClient
|
||||||
@@ -194,6 +249,66 @@ public partial class MainWindowViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<object>> PopulateAsync(string? searchText, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (searchText == null) return [];
|
||||||
|
|
||||||
|
var lcuSchemaDocument = await _documentService.GetLcuSchemaDocumentAsync(cancellationToken);
|
||||||
|
var gameClientDocument = await _documentService.GetLolClientDocumentAsync(cancellationToken);
|
||||||
|
var lcuResults = lcuSchemaDocument.OpenApiDocument.Components.Schemas.Keys.Where(key => key.Contains(searchText, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(key => new SchemaSearchDetailsViewModel(key, Pages.Endpoints.Tab.LCU));
|
||||||
|
var gameClientResults = gameClientDocument.OpenApiDocument.Components.Schemas.Keys.Where(key => key.Contains(searchText, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(key => new SchemaSearchDetailsViewModel(key, Pages.Endpoints.Tab.GameClient));
|
||||||
|
|
||||||
|
return Enumerable.Concat(lcuResults, gameClientResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(CanOpenSchemaPane))]
|
||||||
|
private void OpenSchemaPane()
|
||||||
|
{
|
||||||
|
IsPaneOpen = !IsPaneOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanOpenSchemaPane()
|
||||||
|
{
|
||||||
|
return Schemas.Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(CanCloseSchema))]
|
||||||
|
private void CloseSchema()
|
||||||
|
{
|
||||||
|
if (SelectedSchema is SchemaViewModel selection)
|
||||||
|
{
|
||||||
|
SelectedSchema = null;
|
||||||
|
Schemas = new ObservableCollection<SchemaViewModel>(Schemas.Where(schema => schema != selection));
|
||||||
|
|
||||||
|
OpenSchemaPaneCommand.NotifyCanExecuteChanged();
|
||||||
|
CloseSchemaCommand.NotifyCanExecuteChanged();
|
||||||
|
CloseSchemaAllCommand.NotifyCanExecuteChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanCloseSchema()
|
||||||
|
{
|
||||||
|
return SelectedSchema != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(CanCloseSchemaAll))]
|
||||||
|
private void CloseSchemaAll()
|
||||||
|
{
|
||||||
|
SelectedSchema = null;
|
||||||
|
Schemas = [];
|
||||||
|
|
||||||
|
OpenSchemaPaneCommand.NotifyCanExecuteChanged();
|
||||||
|
CloseSchemaCommand.NotifyCanExecuteChanged();
|
||||||
|
CloseSchemaAllCommand.NotifyCanExecuteChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanCloseSchemaAll()
|
||||||
|
{
|
||||||
|
return Schemas.Any();
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private void OpenUrl(string url)
|
private void OpenUrl(string url)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.MainWindow
|
||||||
|
{
|
||||||
|
public partial class SchemaSearchDetailsViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public SchemaSearchDetailsViewModel(string key, Tab tab)
|
||||||
|
{
|
||||||
|
Tab = tab;
|
||||||
|
Key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Key { get; }
|
||||||
|
|
||||||
|
public Tab Tab { get; }
|
||||||
|
|
||||||
|
public string Document => Tab switch
|
||||||
|
{
|
||||||
|
Tab.LCU => "LCU",
|
||||||
|
Tab.GameClient => "Game Client",
|
||||||
|
_ => throw new System.NotImplementedException()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Needlework.Net/ViewModels/MainWindow/SchemaViewModel.cs
Normal file
22
Needlework.Net/ViewModels/MainWindow/SchemaViewModel.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Needlework.Net.ViewModels.MainWindow
|
||||||
|
{
|
||||||
|
public partial class SchemaViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public SchemaViewModel(PropertyClassViewModel vm)
|
||||||
|
{
|
||||||
|
Id = vm.Id;
|
||||||
|
PropertyFields = vm.PropertyFields;
|
||||||
|
PropertyEnums = vm.PropertyEnums;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id { get; }
|
||||||
|
|
||||||
|
public List<PropertyFieldViewModel> PropertyFields { get; } = [];
|
||||||
|
|
||||||
|
public List<PropertyEnumViewModel> PropertyEnums { get; } = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using Needlework.Net.Models;
|
using Needlework.Net.Helpers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
namespace Needlework.Net.ViewModels.Pages.Endpoints;
|
||||||
|
|
||||||
@@ -15,13 +13,13 @@ public partial class OperationViewModel : ObservableObject
|
|||||||
Summary = operation.Summary ?? string.Empty;
|
Summary = operation.Summary ?? string.Empty;
|
||||||
Description = operation.Description ?? string.Empty;
|
Description = operation.Description ?? string.Empty;
|
||||||
IsRequestBody = operation.RequestBody != null;
|
IsRequestBody = operation.RequestBody != null;
|
||||||
ReturnType = GetReturnType(operation.Responses);
|
ReturnType = OpenApiHelpers.GetReturnType(operation.Responses);
|
||||||
RequestClasses = GetRequestClasses(operation.RequestBody, document);
|
RequestClasses = OpenApiHelpers.GetRequestClasses(operation.RequestBody, document);
|
||||||
ResponseClasses = GetResponseClasses(operation.Responses, document);
|
ResponseClasses = OpenApiHelpers.GetResponseClasses(operation.Responses, document);
|
||||||
PathParameters = GetParameters(operation.Parameters.ToList(), ParameterLocation.Path);
|
PathParameters = OpenApiHelpers.GetParameters(operation.Parameters.ToList(), ParameterLocation.Path);
|
||||||
QueryParameters = GetParameters(operation.Parameters.ToList(), ParameterLocation.Query);
|
QueryParameters = OpenApiHelpers.GetParameters(operation.Parameters.ToList(), ParameterLocation.Query);
|
||||||
RequestBodyType = GetRequestBodyType(operation.RequestBody);
|
RequestBodyType = OpenApiHelpers.GetRequestBodyType(operation.RequestBody);
|
||||||
RequestTemplate = GetRequestTemplate(operation.RequestBody, document);
|
RequestTemplate = OpenApiHelpers.GetRequestTemplate(operation.RequestBody, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Summary { get; }
|
public string Summary { get; }
|
||||||
@@ -43,226 +41,4 @@ public partial class OperationViewModel : ObservableObject
|
|||||||
public List<ParameterViewModel> QueryParameters { get; }
|
public List<ParameterViewModel> QueryParameters { get; }
|
||||||
|
|
||||||
public string? RequestTemplate { get; }
|
public string? RequestTemplate { get; }
|
||||||
|
|
||||||
private string? GetRequestTemplate(OpenApiRequestBody? requestBody, Document document)
|
|
||||||
{
|
|
||||||
var requestClasses = GetRequestClasses(requestBody, document);
|
|
||||||
if (requestClasses.Count == 0)
|
|
||||||
{
|
|
||||||
var type = GetRequestBodyType(requestBody);
|
|
||||||
if (type == null) return null;
|
|
||||||
return GetRequestDefaultValue(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
var template = CreateTemplate(requestClasses);
|
|
||||||
return JsonSerializer.Serialize(JsonSerializer.Deserialize<object>(string.Join(string.Empty, template)), App.JsonSerializerOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<string> CreateTemplate(List<PropertyClassViewModel> requestClasses)
|
|
||||||
{
|
|
||||||
if (requestClasses.Count == 0) return [];
|
|
||||||
List<string> template = [];
|
|
||||||
template.Add("{");
|
|
||||||
|
|
||||||
var rootClass = requestClasses.First();
|
|
||||||
if (rootClass.PropertyEnums.Any()) return [rootClass.PropertyEnums.First().Values];
|
|
||||||
var propertyFields = rootClass.PropertyFields;
|
|
||||||
for (int i = 0; i < propertyFields.Count; i++)
|
|
||||||
{
|
|
||||||
template.Add($"\"{propertyFields[i].Name}\"");
|
|
||||||
template.Add(":");
|
|
||||||
template.Add($"#{propertyFields[i].Type}");
|
|
||||||
|
|
||||||
if (i == propertyFields.Count - 1) template.Add("}");
|
|
||||||
else template.Add(",");
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < template.Count; i++)
|
|
||||||
{
|
|
||||||
var type = template[i];
|
|
||||||
if (!type.Contains("#")) continue;
|
|
||||||
|
|
||||||
var foundClass = requestClasses.Where(c => c.Id == type.Replace("#", string.Empty));
|
|
||||||
if (foundClass.Any())
|
|
||||||
{
|
|
||||||
if (foundClass.First().PropertyEnums.Any())
|
|
||||||
{
|
|
||||||
template[i] = string.Join(string.Empty, CreateTemplate([.. foundClass]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
List<PropertyClassViewModel> classes = [.. requestClasses];
|
|
||||||
classes.Remove(rootClass);
|
|
||||||
template[i] = string.Join(string.Empty, CreateTemplate(classes));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
template[i] = GetRequestDefaultValue(type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return template;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetRequestDefaultValue(string type)
|
|
||||||
{
|
|
||||||
var defaultValue = string.Empty;
|
|
||||||
if (type.Contains("[]")) defaultValue = "[]";
|
|
||||||
else if (type.Contains("string")) defaultValue = "\"\"";
|
|
||||||
else if (type.Contains("boolean")) defaultValue = "false";
|
|
||||||
else if (type.Contains("integer")) defaultValue = "0";
|
|
||||||
else if (type.Contains("double") || type.Contains("float")) defaultValue = "0.0";
|
|
||||||
else if (type.Contains("object")) defaultValue = "{}";
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? GetRequestBodyType(OpenApiRequestBody? requestBody)
|
|
||||||
{
|
|
||||||
if (requestBody == null) return null;
|
|
||||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
|
||||||
{
|
|
||||||
var schema = media.Schema;
|
|
||||||
if (schema == null) return null; // Because "PostLolAccountVerificationV1SendDeactivationPin" exists where the media body is empty...
|
|
||||||
return GetSchemaType(schema);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<ParameterViewModel> GetParameters(List<OpenApiParameter> parameters, ParameterLocation location)
|
|
||||||
{
|
|
||||||
var pathParameters = new List<ParameterViewModel>();
|
|
||||||
foreach (var parameter in parameters)
|
|
||||||
{
|
|
||||||
if (parameter.In != location) continue;
|
|
||||||
pathParameters.Add(new ParameterViewModel(parameter.Name, GetSchemaType(parameter.Schema), parameter.Required));
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetResponse(OpenApiResponses responses, [NotNullWhen(true)] out OpenApiResponse? response)
|
|
||||||
{
|
|
||||||
response = null;
|
|
||||||
var flag = false;
|
|
||||||
if (responses.TryGetValue("2XX", out var x))
|
|
||||||
{
|
|
||||||
response = x;
|
|
||||||
flag = true;
|
|
||||||
}
|
|
||||||
else if (responses.TryGetValue("200", out var y))
|
|
||||||
{
|
|
||||||
response = y;
|
|
||||||
flag = true;
|
|
||||||
}
|
|
||||||
return flag;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<PropertyClassViewModel> GetResponseClasses(OpenApiResponses responses, Document document)
|
|
||||||
{
|
|
||||||
if (!TryGetResponse(responses, out var response))
|
|
||||||
return [];
|
|
||||||
|
|
||||||
if (response.Content.TryGetValue("application/json", out var media))
|
|
||||||
{
|
|
||||||
var rawDocument = document.OpenApiDocument;
|
|
||||||
var schema = media.Schema;
|
|
||||||
if (schema == null) return [];
|
|
||||||
|
|
||||||
List<PropertyClassViewModel> propertyClasses = [];
|
|
||||||
WalkSchema(schema, propertyClasses, rawDocument);
|
|
||||||
return propertyClasses;
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WalkSchema(OpenApiSchema schema, List<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);
|
|
||||||
|
|
||||||
if (propertyClasses.Where(c => c.Id == componentId).Any()) return; // Avoid adding duplicate schemas in classes
|
|
||||||
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 List<PropertyClassViewModel> GetRequestClasses(OpenApiRequestBody? requestBody, Document document)
|
|
||||||
{
|
|
||||||
if (requestBody == null) return [];
|
|
||||||
if (requestBody.Content.TryGetValue("application/json", out var media))
|
|
||||||
{
|
|
||||||
var rawDocument = document.OpenApiDocument;
|
|
||||||
var schema = media.Schema;
|
|
||||||
if (schema == null) return [];
|
|
||||||
|
|
||||||
var type = GetSchemaType(media.Schema);
|
|
||||||
if (IsComponent(type))
|
|
||||||
{
|
|
||||||
var componentId = GetComponentId(schema);
|
|
||||||
var componentSchema = rawDocument.Components.Schemas[componentId];
|
|
||||||
List<PropertyClassViewModel> propertyClasses = [];
|
|
||||||
WalkSchema(componentSchema, propertyClasses, rawDocument);
|
|
||||||
return propertyClasses;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetReturnType(OpenApiResponses responses)
|
|
||||||
{
|
|
||||||
if (!TryGetResponse(responses, out var response))
|
|
||||||
return "none";
|
|
||||||
|
|
||||||
if (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 == null) return "object"; // Because GetLolVanguardV1Notification exists where it has a required parameter without a type...
|
|
||||||
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.AdditionalProperties?.Reference != null) return schema.AdditionalProperties.Reference.Id;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using Microsoft.OpenApi.Any;
|
using Microsoft.OpenApi.Any;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Needlework.Net.Helpers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ public class PropertyClassViewModel : ObservableObject
|
|||||||
List<PropertyEnumViewModel> propertyEnums = [];
|
List<PropertyEnumViewModel> propertyEnums = [];
|
||||||
foreach ((var propertyName, var propertySchema) in properties)
|
foreach ((var propertyName, var propertySchema) in properties)
|
||||||
{
|
{
|
||||||
var type = OperationViewModel.GetSchemaType(propertySchema);
|
var type = OpenApiHelpers.GetSchemaType(propertySchema);
|
||||||
var field = new PropertyFieldViewModel(propertyName, type);
|
var field = new PropertyFieldViewModel(propertyName, type);
|
||||||
propertyFields.Add(field);
|
propertyFields.Add(field);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
Width="1280"
|
Width="1280"
|
||||||
Height="720">
|
Height="720">
|
||||||
<Grid RowDefinitions="auto,*">
|
<Grid RowDefinitions="auto,*">
|
||||||
<Grid ColumnDefinitions="auto,auto,*,auto"
|
<Grid Name="TitleBarHost"
|
||||||
|
ColumnDefinitions="auto,auto,*,auto"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Height="40"
|
Height="40"
|
||||||
Grid.Row="0">
|
Grid.Row="0">
|
||||||
@@ -32,7 +33,37 @@
|
|||||||
IsHitTestVisible="False"
|
IsHitTestVisible="False"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Grid.Column="1"/>
|
Grid.Column="1"/>
|
||||||
|
<Border Grid.Column="2" Padding="4" IsHitTestVisible="True">
|
||||||
|
<StackPanel HorizontalAlignment="Center"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<AutoCompleteBox Name="SchemaAutoCompleteBox"
|
||||||
|
Margin="0 0 8 0"
|
||||||
|
MinWidth="200"
|
||||||
|
MaxWidth="500"
|
||||||
|
MaxDropDownHeight="400"
|
||||||
|
Watermark="Search schemas"
|
||||||
|
FilterMode="None"
|
||||||
|
ValueMemberBinding="{Binding Key, DataType=vm:SchemaSearchDetailsViewModel}"
|
||||||
|
AsyncPopulator="{Binding PopulateAsync}"
|
||||||
|
SelectedItem="{Binding SelectedSchemaSearchDetails}">
|
||||||
|
<AutoCompleteBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="vm:SchemaSearchDetailsViewModel">
|
||||||
|
<ContentControl Content="{Binding}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</AutoCompleteBox.ItemTemplate>
|
||||||
|
</AutoCompleteBox>
|
||||||
|
<Button i:Attached.Icon="fa-solid fa-file-lines"
|
||||||
|
FontSize="20"
|
||||||
|
Command="{Binding OpenSchemaPaneCommand}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<SplitView Grid.Row="1"
|
||||||
|
PaneBackground="Transparent"
|
||||||
|
IsPaneOpen="{Binding IsPaneOpen}"
|
||||||
|
DisplayMode="Inline"
|
||||||
|
OpenPaneLength="350"
|
||||||
|
PanePlacement="Right">
|
||||||
<ui:NavigationView AlwaysShowHeader="False"
|
<ui:NavigationView AlwaysShowHeader="False"
|
||||||
PaneDisplayMode="Left"
|
PaneDisplayMode="Left"
|
||||||
IsSettingsVisible="False"
|
IsSettingsVisible="False"
|
||||||
@@ -84,5 +115,33 @@
|
|||||||
</ItemsControl>
|
</ItemsControl>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ui:NavigationView>
|
</ui:NavigationView>
|
||||||
|
<SplitView.Pane>
|
||||||
|
<StackPanel>
|
||||||
|
<ui:CommandBar DefaultLabelPosition="Right">
|
||||||
|
<ui:CommandBar.PrimaryCommands>
|
||||||
|
<ui:CommandBarButton Name="CloseCommandBarButton" Label="Close" Command="{Binding CloseSchemaCommand}"/>
|
||||||
|
<ui:CommandBarButton Name="CloseAllCommandBarButton" Label="Close all" Command="{Binding CloseSchemaAllCommand}"/>
|
||||||
|
</ui:CommandBar.PrimaryCommands>
|
||||||
|
</ui:CommandBar>
|
||||||
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
|
||||||
|
Margin="8 0 8 0">
|
||||||
|
<ListBox ItemsSource="{Binding Schemas}" SelectedItem="{Binding SelectedSchema}">
|
||||||
|
<ListBox.Styles>
|
||||||
|
<Style Selector="ListBoxItem">
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
|
||||||
|
<Setter Property="Padding" Value="0"></Setter>
|
||||||
|
<Setter Property="Margin" Value="0 0 0 8"></Setter>
|
||||||
|
</Style>
|
||||||
|
</ListBox.Styles>
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ContentControl Content="{Binding}"/>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
</ListBox>
|
||||||
|
</ScrollViewer>
|
||||||
|
</StackPanel>
|
||||||
|
</SplitView.Pane>
|
||||||
|
</SplitView>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Media;
|
||||||
|
using FluentAvalonia.UI.Controls;
|
||||||
using FluentAvalonia.UI.Windowing;
|
using FluentAvalonia.UI.Windowing;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Needlework.Net.Views.MainWindow;
|
namespace Needlework.Net.Views.MainWindow;
|
||||||
|
|
||||||
@@ -10,7 +15,35 @@ public partial class MainWindowView : AppWindow
|
|||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
TitleBar.ExtendsContentIntoTitleBar = true;
|
TitleBar.ExtendsContentIntoTitleBar = true;
|
||||||
|
TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex;
|
||||||
TransparencyLevelHint = [WindowTransparencyLevel.Mica, WindowTransparencyLevel.None];
|
TransparencyLevelHint = [WindowTransparencyLevel.Mica, WindowTransparencyLevel.None];
|
||||||
Background = IsWindows11 ? null : Background;
|
Background = IsWindows11 ? null : Background;
|
||||||
|
|
||||||
|
SchemaAutoCompleteBox.MinimumPopulateDelay = TimeSpan.FromSeconds(1);
|
||||||
|
SchemaAutoCompleteBox.MinimumPrefixLength = 3;
|
||||||
|
|
||||||
|
CloseCommandBarButton.IconSource = new ImageIconSource
|
||||||
|
{
|
||||||
|
Source = new Projektanker.Icons.Avalonia.IconImage()
|
||||||
|
{
|
||||||
|
Value = "fa-solid fa-file-circle-xmark",
|
||||||
|
Brush = new SolidColorBrush(Application.Current!.ActualThemeVariant.Key switch
|
||||||
|
{
|
||||||
|
"Light" => Colors.Black,
|
||||||
|
"Dark" => Colors.White,
|
||||||
|
_ => Colors.Gray
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnLoaded(e);
|
||||||
|
|
||||||
|
if (VisualRoot is AppWindow aw)
|
||||||
|
{
|
||||||
|
TitleBarHost.ColumnDefinitions[3].Width = new GridLength(aw.TitleBar.RightInset, GridUnitType.Pixel);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<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:vm="using:Needlework.Net.ViewModels.MainWindow"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Needlework.Net.Views.MainWindow.SchemaSearchDetailsView"
|
||||||
|
x:DataType="vm:SchemaSearchDetailsViewModel">
|
||||||
|
<StackPanel HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center">
|
||||||
|
<TextBlock Text="{Binding Key}"/>
|
||||||
|
<TextBlock Text="{Binding Document}"
|
||||||
|
Theme="{StaticResource CaptionTextBlockStyle}"
|
||||||
|
Foreground="{DynamicResource AccentTextFillColorPrimaryBrush}"/>
|
||||||
|
</StackPanel>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.MainWindow;
|
||||||
|
|
||||||
|
public partial class SchemaSearchDetailsView : UserControl
|
||||||
|
{
|
||||||
|
public SchemaSearchDetailsView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
42
Needlework.Net/Views/MainWindow/SchemaView.axaml
Normal file
42
Needlework.Net/Views/MainWindow/SchemaView.axaml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<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:vm="using:Needlework.Net.ViewModels.MainWindow"
|
||||||
|
xmlns:controls="using:Needlework.Net.Controls"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="Needlework.Net.Views.MainWindow.SchemaView"
|
||||||
|
x:DataType="vm:SchemaViewModel">
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="DataGrid">
|
||||||
|
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource ControlElevationBorderBrush}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="DataGridColumnHeader TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource TextFillColorPrimaryBrush}"/>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="DataGridRow DataGridCell">
|
||||||
|
<Setter Property="FontSize" Value="12"></Setter>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="DataGridRow">
|
||||||
|
<Setter Property="Margin" Value="0 0 0 4"></Setter>
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
<controls:Card>
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock FontSize="12" FontWeight="DemiBold" Text="{Binding Id}" Margin="0 0 0 4"/>
|
||||||
|
<DataGrid IsVisible="{Binding PropertyFields, Converter={StaticResource EnumerableToVisibilityConverter}}"
|
||||||
|
ItemsSource="{Binding PropertyFields}"
|
||||||
|
AutoGenerateColumns="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal">
|
||||||
|
</DataGrid>
|
||||||
|
<DataGrid IsVisible="{Binding PropertyEnums, Converter={StaticResource EnumerableToVisibilityConverter}}"
|
||||||
|
Margin="0 0 0 8"
|
||||||
|
ItemsSource="{Binding PropertyEnums}"
|
||||||
|
AutoGenerateColumns="True"
|
||||||
|
IsReadOnly="True"
|
||||||
|
GridLinesVisibility="Horizontal">
|
||||||
|
</DataGrid>
|
||||||
|
</StackPanel>
|
||||||
|
</controls:Card>
|
||||||
|
</UserControl>
|
||||||
11
Needlework.Net/Views/MainWindow/SchemaView.axaml.cs
Normal file
11
Needlework.Net/Views/MainWindow/SchemaView.axaml.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace Needlework.Net.Views.MainWindow;
|
||||||
|
|
||||||
|
public partial class SchemaView : UserControl
|
||||||
|
{
|
||||||
|
public SchemaView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user