CmdPal: Add IFormContent2 support (#50178)

When you create an action in adaptive cards, you can assign an ID to
that action. That ID helps identify what the user activated. It might
_not_ be a part of the action JSON. Sometimes it comes through as a
separate piece of data.

If we wanted to be more technically correct (and we do), then our
`SubmitForm` should also accept an action ID.

This adds an update to `IFormContent` to do just that.
`IFormContent2` adds `SubmitAction` which is just `SubmitForm`, but with
an ID. If you don't manually implement `SubmitAction` in the next
version of the toolkit, we'll forward it along to the old `SubmitForm`.

Built it and ran the sample locally. 

Closes: _future work item_
This commit is contained in:
Mike Griese
2026-08-28 16:47:16 -05:00
committed by GitHub
parent 428044b477
commit 7bf87a308b
6 changed files with 51 additions and 3 deletions

View File

@@ -172,7 +172,9 @@ public partial class ContentFormViewModel(IFormContent _form, WeakReference<IPag
var model = _formModel.Unsafe!; var model = _formModel.Unsafe!;
if (model != null) if (model != null)
{ {
var result = model.SubmitForm(inputString, dataString); var result = model is IFormContent2 form2
? form2.SubmitAction(action.Id, inputString, dataString)
: model.SubmitForm(inputString, dataString);
WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result))); WeakReferenceMessenger.Default.Send<HandleCommandResultMessage>(new(new(result)));
} }
} }

View File

@@ -88,6 +88,7 @@ functionality.
- [Addenda V: Extra content types](#addenda-v-extra-content-types) - [Addenda V: Extra content types](#addenda-v-extra-content-types)
- [Image content](#image-content) - [Image content](#image-content)
- [Plain text content](#plain-text-content) - [Plain text content](#plain-text-content)
- [Addenda VI: Adaptive Card Actions](#addenda-vi-adaptive-card-actions)
- [Class diagram](#class-diagram) - [Class diagram](#class-diagram)
- [Future considerations](#future-considerations) - [Future considerations](#future-considerations)
- [Arbitrary parameters and arguments](#arbitrary-parameters-and-arguments) - [Arbitrary parameters and arguments](#arbitrary-parameters-and-arguments)
@@ -2424,6 +2425,22 @@ interface IPlainTextContent requires IContent {
} }
``` ```
## Addenda VI: Adaptive Card Actions
Adaptive Cards supports setting multiple actions on a card. Those actions can be
identified by an `id` property on the action. That `id` is not necessarily
encoded in the JSON payload of the action.
For us to properly support the gammut of AC scenarios, we need to be able to
pass the `id` of the action back to the extension. This is a relatively simple
addition to the `IForm` interface.
```csharp
interface IFormContent2 requires IFormContent {
ICommandResult SubmitAction(String actionId, String inputs, String data);
}
```
## Class diagram ## Class diagram
This is a diagram attempting to show the relationships between the various types we've defined for the SDK. Some elements are omitted for clarity. (Notably, `IconData` and `IPropChanged`, which are used in many places.) This is a diagram attempting to show the relationships between the various types we've defined for the SDK. Some elements are omitted for clarity. (Notably, `IconData` and `IPropChanged`, which are used in many places.)

View File

@@ -256,6 +256,7 @@ internal sealed partial class SampleContentForm : FormContent
"actions": [ "actions": [
{ {
"type": "Action.Submit", "type": "Action.Submit",
"id": "submit-form",
"title": "Submit", "title": "Submit",
"data": { "data": {
"id": "1234567890" "id": "1234567890"
@@ -277,6 +278,7 @@ internal sealed partial class SampleContentForm : FormContent
"actions": [ "actions": [
{ {
"type": "Action.Submit", "type": "Action.Submit",
"id": "submit-comment",
"title": "OK" "title": "OK"
} }
] ]
@@ -368,6 +370,9 @@ internal sealed partial class SampleContentForm : FormContent
// Application.Current.GetService<ILocalSettingsService>().SaveSettingAsync("GlobalHotkey", formInput["hotkey"]?.ToString() ?? string.Empty); // Application.Current.GetService<ILocalSettingsService>().SaveSettingAsync("GlobalHotkey", formInput["hotkey"]?.ToString() ?? string.Empty);
return CommandResult.GoHome(); return CommandResult.GoHome();
} }
public override CommandResult SubmitAction(string actionId, string inputs, string data) =>
CommandResult.ShowToast($"Submitted action: {actionId}");
} }
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")] [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Sample code")]

View File

@@ -78,7 +78,7 @@ public abstract partial class CommandProvider :
/// <returns>an array of objects that implement all the leaf interfaces we support</returns> /// <returns>an array of objects that implement all the leaf interfaces we support</returns>
public object[] GetApiExtensionStubs() public object[] GetApiExtensionStubs()
{ {
return [new SupportCommandsWithProperties()]; return [new SupportCommandsWithProperties(), new SupportFormActions()];
} }
/// <summary> /// <summary>
@@ -90,4 +90,21 @@ public abstract partial class CommandProvider :
{ {
public IDictionary<string, object>? GetProperties() => null; public IDictionary<string, object>? GetProperties() => null;
} }
private sealed partial class SupportFormActions : IFormContent2
{
public string TemplateJson => string.Empty;
public string DataJson => string.Empty;
public string StateJson => string.Empty;
public ICommandResult SubmitForm(string inputs, string data) => CommandResult.KeepOpen();
public ICommandResult SubmitAction(string actionId, string inputs, string data) => CommandResult.KeepOpen();
#pragma warning disable CS0067
public event Windows.Foundation.TypedEventHandler<object, IPropChangedEventArgs>? PropChanged;
#pragma warning restore CS0067
}
} }

View File

@@ -4,7 +4,7 @@
namespace Microsoft.CommandPalette.Extensions.Toolkit; namespace Microsoft.CommandPalette.Extensions.Toolkit;
public partial class FormContent : BaseObservable, IFormContent public partial class FormContent : BaseObservable, IFormContent2
{ {
public virtual string DataJson { get; set => SetProperty(ref field, value); } = string.Empty; public virtual string DataJson { get; set => SetProperty(ref field, value); } = string.Empty;
@@ -15,4 +15,6 @@ public partial class FormContent : BaseObservable, IFormContent
public virtual ICommandResult SubmitForm(string inputs, string data) => SubmitForm(inputs); public virtual ICommandResult SubmitForm(string inputs, string data) => SubmitForm(inputs);
public virtual ICommandResult SubmitForm(string inputs) => CommandResult.KeepOpen(); public virtual ICommandResult SubmitForm(string inputs) => CommandResult.KeepOpen();
public virtual ICommandResult SubmitAction(string actionId, string inputs, string data) => SubmitForm(inputs, data);
} }

View File

@@ -490,5 +490,10 @@ namespace Microsoft.CommandPalette.Extensions
Boolean WrapWords { get; }; Boolean WrapWords { get; };
} }
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
interface IFormContent2 requires IFormContent {
ICommandResult SubmitAction(String actionId, String inputs, String data);
}
} }