/ src / modules / cmdpal / Microsoft.CmdPal.UI.ViewModels / ContentFormViewModel.cs
ContentFormViewModel.cs
  1  // Copyright (c) Microsoft Corporation
  2  // The Microsoft Corporation licenses this file to you under the MIT license.
  3  // See the LICENSE file in the project root for more information.
  4  
  5  using System.Diagnostics.CodeAnalysis;
  6  using System.Text.Json;
  7  using AdaptiveCards.ObjectModel.WinUI3;
  8  using AdaptiveCards.Templating;
  9  using CommunityToolkit.Mvvm.Messaging;
 10  using ManagedCommon;
 11  using Microsoft.CmdPal.Core.ViewModels;
 12  using Microsoft.CmdPal.Core.ViewModels.Messages;
 13  using Microsoft.CmdPal.Core.ViewModels.Models;
 14  using Microsoft.CommandPalette.Extensions;
 15  using Windows.Data.Json;
 16  
 17  namespace Microsoft.CmdPal.UI.ViewModels;
 18  
 19  public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPageContext> context) :
 20      ContentViewModel(context)
 21  {
 22      private readonly ExtensionObject<IFormContent> _formModel = new(_form);
 23  
 24      // Remember - "observable" properties from the model (via PropChanged)
 25      // cannot be marked [ObservableProperty]
 26      public string TemplateJson { get; protected set; } = "{}";
 27  
 28      public string StateJson { get; protected set; } = "{}";
 29  
 30      public string DataJson { get; protected set; } = "{}";
 31  
 32      public AdaptiveCardParseResult? Card { get; private set; }
 33  
 34      private static string Serialize(string? s) =>
 35          JsonSerializer.Serialize(s, JsonSerializationContext.Default.String);
 36  
 37      private static bool TryBuildCard(
 38          string templateJson,
 39          string dataJson,
 40          out AdaptiveCardParseResult? card,
 41          out Exception? error)
 42      {
 43          card = null;
 44          error = null;
 45  
 46          try
 47          {
 48              var template = new AdaptiveCardTemplate(templateJson);
 49              var cardJson = template.Expand(dataJson);
 50              card = AdaptiveCard.FromJsonString(cardJson);
 51              return true;
 52          }
 53          catch (Exception ex)
 54          {
 55              Logger.LogError("Error building card from template", ex);
 56              error = ex;
 57              return false;
 58          }
 59      }
 60  
 61      public override void InitializeProperties()
 62      {
 63          var model = _formModel.Unsafe;
 64          if (model is null)
 65          {
 66              return;
 67          }
 68  
 69          TemplateJson = model.TemplateJson;
 70          StateJson = model.StateJson;
 71          DataJson = model.DataJson;
 72  
 73          if (TryBuildCard(TemplateJson, DataJson, out var builtCard, out var renderingError))
 74          {
 75              Card = builtCard;
 76              UpdateProperty(nameof(Card));
 77              return;
 78          }
 79  
 80          var errorPayload = $$"""
 81      {
 82          "error_message": {{Serialize(renderingError!.Message)}},
 83          "error_stack":   {{Serialize(renderingError.StackTrace)}},
 84          "inner_exception": {{Serialize(renderingError.InnerException?.Message)}},
 85          "template_json": {{Serialize(TemplateJson)}},
 86          "data_json":     {{Serialize(DataJson)}}
 87      }
 88      """;
 89  
 90          if (TryBuildCard(ErrorCardJson, errorPayload, out var errorCard, out var _))
 91          {
 92              Card = errorCard;
 93              UpdateProperty(nameof(Card));
 94              return;
 95          }
 96  
 97          UpdateProperty(nameof(Card));
 98      }
 99  
100      [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(AdaptiveOpenUrlAction))]
101      [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(AdaptiveSubmitAction))]
102      [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(AdaptiveExecuteAction))]
103      public void HandleSubmit(IAdaptiveActionElement action, JsonObject inputs)
104      {
105          if (action is AdaptiveOpenUrlAction openUrlAction)
106          {
107              WeakReferenceMessenger.Default.Send<LaunchUriMessage>(new(openUrlAction.Url));
108              return;
109          }
110  
111          if (action is AdaptiveSubmitAction or AdaptiveExecuteAction)
112          {
113              // Get the data and inputs
114              var dataString = (action as AdaptiveSubmitAction)?.DataJson.Stringify() ?? string.Empty;
115              var inputString = inputs.Stringify();
116  
117              _ = Task.Run(() =>
118              {
119                  try
120                  {
121                      var model = _formModel.Unsafe!;
122                      if (model != null)
123                      {
124                          var result = model.SubmitForm(inputString, dataString);
125                          WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result)));
126                      }
127                  }
128                  catch (Exception ex)
129                  {
130                      ShowException(ex);
131                  }
132              });
133          }
134      }
135  
136      private static readonly string ErrorCardJson = """
137  {
138      "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
139      "type": "AdaptiveCard",
140      "version": "1.5",
141      "body": [
142          {
143              "type": "TextBlock",
144              "text": "Error parsing form from extension",
145              "wrap": true,
146              "style": "heading",
147              "size": "ExtraLarge",
148              "weight": "Bolder",
149              "color": "Attention"
150          },
151          {
152              "type": "TextBlock",
153              "wrap": true,
154              "text": "${error_message}",
155              "color": "Attention"
156          },
157          {
158              "type": "TextBlock",
159              "text": "${error_stack}",
160              "fontType": "Monospace"
161          },
162          {
163              "type": "TextBlock",
164              "wrap": true,
165              "text": "Inner exception:"
166          },
167          {
168              "type": "TextBlock",
169              "wrap": true,
170              "text": "${inner_exception}",
171              "color": "Attention"
172          }
173      ]
174  }
175  """;
176  }