diff --git a/LANCommander.SDK/Models/OptionSchema.cs b/LANCommander.SDK/Models/OptionSchema.cs index 31cfadfd..2a85b501 100644 --- a/LANCommander.SDK/Models/OptionSchema.cs +++ b/LANCommander.SDK/Models/OptionSchema.cs @@ -1,4 +1,8 @@ +using System; using System.Collections.Generic; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; namespace LANCommander.SDK.Models { @@ -36,6 +40,28 @@ namespace LANCommander.SDK.Models } } + public class OptionChoice + { + public string Value { get; set; } + public string DisplayName { get; set; } + + [YamlIgnore] + public string Label => !string.IsNullOrWhiteSpace(DisplayName) ? DisplayName : Value; + + public OptionChoice() { } + + public OptionChoice(string value) + { + Value = value; + } + + public OptionChoice(string value, string displayName) + { + Value = value; + DisplayName = displayName; + } + } + public class OptionDefinition { public string Type { get; set; } @@ -44,7 +70,69 @@ namespace LANCommander.SDK.Models public string Default { get; set; } public string Description { get; set; } public bool Required { get; set; } - public List Choices { get; set; } + public List Choices { get; set; } public Dictionary Options { get; set; } } + + /// + /// Handles deserializing OptionChoice from both plain strings and mapping objects. + /// Plain string "foo" becomes OptionChoice { Value = "foo" }. + /// Mapping { Value: "foo", DisplayName: "Foo" } becomes OptionChoice { Value = "foo", DisplayName = "Foo" }. + /// + public class OptionChoiceYamlConverter : IYamlTypeConverter + { + public bool Accepts(Type type) => type == typeof(OptionChoice); + + public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) + { + if (parser.TryConsume(out var scalar)) + { + return new OptionChoice(scalar.Value); + } + + if (parser.TryConsume(out _)) + { + var choice = new OptionChoice(); + + while (!parser.TryConsume(out _)) + { + var key = parser.Consume(); + var value = parser.Consume(); + + switch (key.Value) + { + case "Value": + choice.Value = value.Value; + break; + case "DisplayName": + choice.DisplayName = value.Value; + break; + } + } + + return choice; + } + + throw new YamlException("Expected a scalar or mapping for OptionChoice"); + } + + public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) + { + var choice = (OptionChoice)value; + + if (string.IsNullOrWhiteSpace(choice.DisplayName)) + { + emitter.Emit(new Scalar(choice.Value)); + } + else + { + emitter.Emit(new MappingStart()); + emitter.Emit(new Scalar("Value")); + emitter.Emit(new Scalar(choice.Value)); + emitter.Emit(new Scalar("DisplayName")); + emitter.Emit(new Scalar(choice.DisplayName)); + emitter.Emit(new MappingEnd()); + } + } + } } diff --git a/LANCommander.SDK/PowerShell/Cmdlets/Get-RedistributableOptions.cs b/LANCommander.SDK/PowerShell/Cmdlets/Get-RedistributableOptions.cs index e01d88b2..a9022966 100644 --- a/LANCommander.SDK/PowerShell/Cmdlets/Get-RedistributableOptions.cs +++ b/LANCommander.SDK/PowerShell/Cmdlets/Get-RedistributableOptions.cs @@ -67,6 +67,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets { var deserializer = new DeserializerBuilder() .WithNamingConvention(PascalCaseNamingConvention.Instance) + .WithTypeConverter(new SDK.Models.OptionChoiceYamlConverter()) .IgnoreUnmatchedProperties() .Build(); diff --git a/LANCommander.SDK/ProcessExecutionContext.cs b/LANCommander.SDK/ProcessExecutionContext.cs index 55c4bd70..a440b005 100644 --- a/LANCommander.SDK/ProcessExecutionContext.cs +++ b/LANCommander.SDK/ProcessExecutionContext.cs @@ -236,6 +236,7 @@ namespace LANCommander.SDK { var deserializer = new DeserializerBuilder() .WithNamingConvention(PascalCaseNamingConvention.Instance) + .WithTypeConverter(new OptionChoiceYamlConverter()) .IgnoreUnmatchedProperties() .Build(); diff --git a/LANCommander.Server.Services/ConfigToOptionSchemaService.cs b/LANCommander.Server.Services/ConfigToOptionSchemaService.cs index e4ad470c..c0bcac96 100644 --- a/LANCommander.Server.Services/ConfigToOptionSchemaService.cs +++ b/LANCommander.Server.Services/ConfigToOptionSchemaService.cs @@ -26,6 +26,7 @@ public class ConfigToOptionSchemaService var serializer = new SerializerBuilder() .WithNamingConvention(PascalCaseNamingConvention.Instance) + .WithTypeConverter(new OptionChoiceYamlConverter()) .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull | DefaultValuesHandling.OmitDefaults | DefaultValuesHandling.OmitEmptyCollections) .Build(); @@ -153,13 +154,13 @@ public class ConfigToOptionSchemaService break; case JsonValueKind.Array: - var choices = new List(); + var choices = new List(); foreach (var item in prop.Value.EnumerateArray()) { if (item.ValueKind == JsonValueKind.String) - choices.Add(item.GetString() ?? ""); + choices.Add(new OptionChoice(item.GetString() ?? "")); else - choices.Add(item.GetRawText()); + choices.Add(new OptionChoice(item.GetRawText())); } options[SanitizeKey(prop.Name)] = new OptionDefinition diff --git a/LANCommander.Server/UI/Components/AddOptionDialog.razor b/LANCommander.Server/UI/Components/AddOptionDialog.razor index 6f3d7572..4e565376 100644 --- a/LANCommander.Server/UI/Components/AddOptionDialog.razor +++ b/LANCommander.Server/UI/Components/AddOptionDialog.razor @@ -28,8 +28,8 @@ @if (_model.Type == "choice") { - -