diff --git a/Needlework.Net/Helpers/OpenApiHelpers.cs b/Needlework.Net/Helpers/OpenApiHelpers.cs new file mode 100644 index 0000000..e30121e --- /dev/null +++ b/Needlework.Net/Helpers/OpenApiHelpers.cs @@ -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 CreateTemplate(List requestClasses) + { + if (requestClasses.Count == 0) return []; + List 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 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 GetParameters(List parameters, ParameterLocation location) + { + var pathParameters = new List(); + 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 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 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(string.Join(string.Empty, template)), App.JsonSerializerOptions); + } + + public static List 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 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 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; + } + } +} \ No newline at end of file diff --git a/Needlework.Net/Needlework.Net.csproj b/Needlework.Net/Needlework.Net.csproj index 4111108..9620668 100644 --- a/Needlework.Net/Needlework.Net.csproj +++ b/Needlework.Net/Needlework.Net.csproj @@ -17,15 +17,15 @@ - + - + - - + + - - + + diff --git a/Needlework.Net/Services/DocumentService.cs b/Needlework.Net/Services/DocumentService.cs index 291e7c8..d3522b8 100644 --- a/Needlework.Net/Services/DocumentService.cs +++ b/Needlework.Net/Services/DocumentService.cs @@ -5,6 +5,7 @@ using Microsoft.OpenApi.Readers; using Needlework.Net.Extensions; using Needlework.Net.Models; using System; +using System.Threading; using System.Threading.Tasks; namespace Needlework.Net @@ -20,7 +21,7 @@ namespace Needlework.Net _githubUserContentClient = clients.Get("GithubUserContentClient"); } - public async Task GetLcuSchemaDocumentAsync() + public async Task GetLcuSchemaDocumentAsync(CancellationToken cancellationToken = default) { if (Cached.TryGet(nameof(GetLcuSchemaDocumentAsync), out var cached)) { @@ -28,14 +29,14 @@ namespace Needlework.Net } var lcuSchemaStream = await _githubUserContentClient.Request("/dysolix/hasagi-types/main/swagger.json") - .GetStreamAsync(); + .GetStreamAsync(cancellationToken: cancellationToken); var lcuSchemaRaw = _reader.Read(lcuSchemaStream, out var _); var document = new Document(lcuSchemaRaw); return cached.Save(document, TimeSpan.FromMinutes(60)); } - public async Task GetLolClientDocumentAsync() + public async Task GetLolClientDocumentAsync(CancellationToken cancellationToken = default) { if (Cached.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") - .GetStreamAsync(); + .GetStreamAsync(cancellationToken: cancellationToken); var lolClientRaw = _reader.Read(lolClientStream, out var _); var document = new Document(lolClientRaw); diff --git a/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs b/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs index 58f869e..786be02 100644 --- a/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs +++ b/Needlework.Net/ViewModels/MainWindow/MainWindowViewModel.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.Media; +using Avalonia.Threading; using BlossomiShymae.Briar; using BlossomiShymae.Briar.Utils; using CommunityToolkit.Mvvm.ComponentModel; @@ -9,6 +10,7 @@ using FluentAvalonia.UI.Controls; using Flurl.Http; using Flurl.Http.Configuration; using Needlework.Net.Extensions; +using Needlework.Net.Helpers; using Needlework.Net.Messages; using Needlework.Net.Models; using Needlework.Net.Services; @@ -23,6 +25,7 @@ using System.Net.Http.Json; using System.Reactive; using System.Reactive.Linq; using System.Reflection; +using System.Threading; using System.Threading.Tasks; namespace Needlework.Net.ViewModels.MainWindow; @@ -104,6 +107,15 @@ public partial class MainWindowViewModel WeakReferenceMessenger.Default.RegisterAll(this); } + [ObservableProperty] + private bool _isPaneOpen; + + [ObservableProperty] + private ObservableCollection _schemas = []; + + [ObservableProperty] + private SchemaViewModel? _selectedSchema; + [ObservableProperty] private ObservableCollection _notifications = []; @@ -113,6 +125,9 @@ public partial class MainWindowViewModel [ObservableProperty] private PageBase _currentPage; + [ObservableProperty] + private SchemaSearchDetailsViewModel? _selectedSchemaSearchDetails; + public List NavigationViewItems { get; private set; } = []; public bool IsSchemaVersionChecked { get; private set; } @@ -121,6 +136,54 @@ public partial class MainWindowViewModel 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 value) + { + if (!value.Any()) + { + IsPaneOpen = false; + } + } + private NavigationViewItem ToNavigationViewItem(PageBase page) => new() { 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() { var release = await _githubClient @@ -194,6 +249,66 @@ public partial class MainWindowViewModel } } + public async Task> 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(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] private void OpenUrl(string url) { diff --git a/Needlework.Net/ViewModels/MainWindow/SchemaSearchDetailsViewModel.cs b/Needlework.Net/ViewModels/MainWindow/SchemaSearchDetailsViewModel.cs new file mode 100644 index 0000000..18c09e0 --- /dev/null +++ b/Needlework.Net/ViewModels/MainWindow/SchemaSearchDetailsViewModel.cs @@ -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() + }; + } +} diff --git a/Needlework.Net/ViewModels/MainWindow/SchemaViewModel.cs b/Needlework.Net/ViewModels/MainWindow/SchemaViewModel.cs new file mode 100644 index 0000000..697dc0a --- /dev/null +++ b/Needlework.Net/ViewModels/MainWindow/SchemaViewModel.cs @@ -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 PropertyFields { get; } = []; + + public List PropertyEnums { get; } = []; + } +} diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs index 71381bf..83745b9 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/OperationViewModel.cs @@ -1,10 +1,8 @@ using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.OpenApi.Models; -using Needlework.Net.Models; +using Needlework.Net.Helpers; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Text.Json; namespace Needlework.Net.ViewModels.Pages.Endpoints; @@ -15,13 +13,13 @@ public partial class OperationViewModel : ObservableObject Summary = operation.Summary ?? string.Empty; Description = operation.Description ?? string.Empty; IsRequestBody = operation.RequestBody != null; - ReturnType = GetReturnType(operation.Responses); - RequestClasses = GetRequestClasses(operation.RequestBody, document); - ResponseClasses = GetResponseClasses(operation.Responses, document); - PathParameters = GetParameters(operation.Parameters.ToList(), ParameterLocation.Path); - QueryParameters = GetParameters(operation.Parameters.ToList(), ParameterLocation.Query); - RequestBodyType = GetRequestBodyType(operation.RequestBody); - RequestTemplate = GetRequestTemplate(operation.RequestBody, document); + ReturnType = OpenApiHelpers.GetReturnType(operation.Responses); + RequestClasses = OpenApiHelpers.GetRequestClasses(operation.RequestBody, document); + ResponseClasses = OpenApiHelpers.GetResponseClasses(operation.Responses, document); + PathParameters = OpenApiHelpers.GetParameters(operation.Parameters.ToList(), ParameterLocation.Path); + QueryParameters = OpenApiHelpers.GetParameters(operation.Parameters.ToList(), ParameterLocation.Query); + RequestBodyType = OpenApiHelpers.GetRequestBodyType(operation.RequestBody); + RequestTemplate = OpenApiHelpers.GetRequestTemplate(operation.RequestBody, document); } public string Summary { get; } @@ -43,226 +41,4 @@ public partial class OperationViewModel : ObservableObject public List QueryParameters { 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(string.Join(string.Empty, template)), App.JsonSerializerOptions); - } - - private List CreateTemplate(List requestClasses) - { - if (requestClasses.Count == 0) return []; - List 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 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 GetParameters(List parameters, ParameterLocation location) - { - var pathParameters = new List(); - 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 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 propertyClasses = []; - WalkSchema(schema, propertyClasses, rawDocument); - return propertyClasses; - } - - return []; - } - - private void WalkSchema(OpenApiSchema schema, List 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 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 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; - } } \ No newline at end of file diff --git a/Needlework.Net/ViewModels/Pages/Endpoints/PropertyClassViewModel.cs b/Needlework.Net/ViewModels/Pages/Endpoints/PropertyClassViewModel.cs index 47b4941..bcaf7d4 100644 --- a/Needlework.Net/ViewModels/Pages/Endpoints/PropertyClassViewModel.cs +++ b/Needlework.Net/ViewModels/Pages/Endpoints/PropertyClassViewModel.cs @@ -1,6 +1,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; +using Needlework.Net.Helpers; using System.Collections.Generic; using System.Linq; @@ -14,7 +15,7 @@ public class PropertyClassViewModel : ObservableObject List propertyEnums = []; foreach ((var propertyName, var propertySchema) in properties) { - var type = OperationViewModel.GetSchemaType(propertySchema); + var type = OpenApiHelpers.GetSchemaType(propertySchema); var field = new PropertyFieldViewModel(propertyName, type); propertyFields.Add(field); } diff --git a/Needlework.Net/Views/MainWindow/MainWindowView.axaml b/Needlework.Net/Views/MainWindow/MainWindowView.axaml index 53849b5..bc1d5f1 100644 --- a/Needlework.Net/Views/MainWindow/MainWindowView.axaml +++ b/Needlework.Net/Views/MainWindow/MainWindowView.axaml @@ -16,7 +16,8 @@ Width="1280" Height="720"> - @@ -32,8 +33,38 @@ IsHitTestVisible="False" VerticalAlignment="Center" Grid.Column="1"/> + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + diff --git a/Needlework.Net/Views/MainWindow/MainWindowView.axaml.cs b/Needlework.Net/Views/MainWindow/MainWindowView.axaml.cs index 49fadfc..09abd45 100644 --- a/Needlework.Net/Views/MainWindow/MainWindowView.axaml.cs +++ b/Needlework.Net/Views/MainWindow/MainWindowView.axaml.cs @@ -1,5 +1,10 @@ +using Avalonia; using Avalonia.Controls; +using Avalonia.Interactivity; +using Avalonia.Media; +using FluentAvalonia.UI.Controls; using FluentAvalonia.UI.Windowing; +using System; namespace Needlework.Net.Views.MainWindow; @@ -10,7 +15,35 @@ public partial class MainWindowView : AppWindow InitializeComponent(); TitleBar.ExtendsContentIntoTitleBar = true; + TitleBar.TitleBarHitTestType = TitleBarHitTestType.Complex; TransparencyLevelHint = [WindowTransparencyLevel.Mica, WindowTransparencyLevel.None]; 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); + } } } \ No newline at end of file diff --git a/Needlework.Net/Views/MainWindow/SchemaSearchDetailsView.axaml b/Needlework.Net/Views/MainWindow/SchemaSearchDetailsView.axaml new file mode 100644 index 0000000..3fc15fb --- /dev/null +++ b/Needlework.Net/Views/MainWindow/SchemaSearchDetailsView.axaml @@ -0,0 +1,16 @@ + + + + + + diff --git a/Needlework.Net/Views/MainWindow/SchemaSearchDetailsView.axaml.cs b/Needlework.Net/Views/MainWindow/SchemaSearchDetailsView.axaml.cs new file mode 100644 index 0000000..05622e9 --- /dev/null +++ b/Needlework.Net/Views/MainWindow/SchemaSearchDetailsView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Views.MainWindow; + +public partial class SchemaSearchDetailsView : UserControl +{ + public SchemaSearchDetailsView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Needlework.Net/Views/MainWindow/SchemaView.axaml b/Needlework.Net/Views/MainWindow/SchemaView.axaml new file mode 100644 index 0000000..df372d1 --- /dev/null +++ b/Needlework.Net/Views/MainWindow/SchemaView.axaml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + diff --git a/Needlework.Net/Views/MainWindow/SchemaView.axaml.cs b/Needlework.Net/Views/MainWindow/SchemaView.axaml.cs new file mode 100644 index 0000000..92acf9e --- /dev/null +++ b/Needlework.Net/Views/MainWindow/SchemaView.axaml.cs @@ -0,0 +1,11 @@ +using Avalonia.Controls; + +namespace Needlework.Net.Views.MainWindow; + +public partial class SchemaView : UserControl +{ + public SchemaView() + { + InitializeComponent(); + } +} \ No newline at end of file