mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-11 23:06:45 +01:00
Merge pull request #219 from zadjii-msft/dev/migrie/f/TRA-forms-pr
Get the other two types of pages back in. Form pages and Markdown pages.  When forms fail to parse, we'll display the exception by replacing the card with one of our one authoring  Markdown pages support multiple bodies, and possibly details:   Ref #73
This commit is contained in:
@@ -148,7 +148,7 @@ public partial class MastodonExtensionActionsProvider : CommandProvider
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
public partial class MastodonPostForm : IForm
|
||||
public partial class MastodonPostForm : Form
|
||||
{
|
||||
private readonly MastodonStatus post;
|
||||
|
||||
@@ -157,7 +157,7 @@ public partial class MastodonPostForm : IForm
|
||||
this.post = post;
|
||||
}
|
||||
|
||||
public string DataJson()
|
||||
public override string DataJson()
|
||||
{
|
||||
return $$"""
|
||||
{
|
||||
@@ -171,11 +171,9 @@ public partial class MastodonPostForm : IForm
|
||||
""";
|
||||
}
|
||||
|
||||
public string StateJson() => throw new NotImplementedException();
|
||||
public override ICommandResult SubmitForm(string payload) => CommandResult.Dismiss();
|
||||
|
||||
public ICommandResult SubmitForm(string payload) => CommandResult.Dismiss();
|
||||
|
||||
public string TemplateJson()
|
||||
public override string TemplateJson()
|
||||
{
|
||||
var img_block = string.Empty;
|
||||
if (post.MediaAttachments.Count > 0)
|
||||
@@ -203,7 +201,7 @@ public partial class MastodonPostForm : IForm
|
||||
{
|
||||
"type": "Image",
|
||||
"url": "${author_avatar_url}",
|
||||
"size": "Small",
|
||||
"size": "Medium",
|
||||
"style": "Person"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -56,7 +56,7 @@ internal sealed partial class AddBookmarkForm : Form
|
||||
|
||||
public override string DataJson() => throw new NotImplementedException();
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
public override string StateJson() => "{}";
|
||||
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
|
||||
@@ -69,7 +69,7 @@ internal sealed partial class BookmarkPlaceholderForm : Form
|
||||
|
||||
public override string DataJson() => throw new NotImplementedException();
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
public override string StateJson() => "{}";
|
||||
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
|
||||
@@ -75,7 +75,7 @@ internal sealed partial class SettingsForm : Form
|
||||
""";
|
||||
}
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
public override string StateJson() => "{}";
|
||||
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
|
||||
@@ -55,7 +55,7 @@ internal sealed partial class SampleForm : Form
|
||||
return json;
|
||||
}
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
public override string StateJson() => "{}";
|
||||
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
|
||||
@@ -10,21 +10,11 @@ namespace SamplePagesExtension;
|
||||
public partial class SamplesListPage : ListPage
|
||||
{
|
||||
private readonly IListItem[] _commands = [
|
||||
new ListItem(new SampleMarkdownPage())
|
||||
{
|
||||
Title = "Markdown Page Sample Command",
|
||||
Subtitle = "Display a page of rendered markdown",
|
||||
},
|
||||
new ListItem(new SampleListPage())
|
||||
{
|
||||
Title = "List Page Sample Command",
|
||||
Subtitle = "Display a list of items",
|
||||
},
|
||||
new ListItem(new SampleFormPage())
|
||||
{
|
||||
Title = "Form Page Sample Command",
|
||||
Subtitle = "Define inputs to retrieve input from the user",
|
||||
},
|
||||
new ListItem(new SampleListPageWithDetails())
|
||||
{
|
||||
Title = "List Page With Details Sample Command",
|
||||
@@ -40,11 +30,34 @@ public partial class SamplesListPage : ListPage
|
||||
Title = "Dynamic List Page Command",
|
||||
Subtitle = "Changes the list of items in response to the typed query",
|
||||
},
|
||||
|
||||
new ListItem(new SampleMarkdownPage())
|
||||
{
|
||||
Title = "Markdown Page Sample Command",
|
||||
Subtitle = "Display a page of rendered markdown",
|
||||
},
|
||||
new ListItem(new SampleMarkdownManyBodies())
|
||||
{
|
||||
Title = "Markdown with multiple blocks",
|
||||
Subtitle = "A page with multiple blocks of rendered markdown",
|
||||
},
|
||||
new ListItem(new SampleMarkdownDetails())
|
||||
{
|
||||
Title = "Markdown with details",
|
||||
Subtitle = "A page with markdown and details",
|
||||
},
|
||||
|
||||
new ListItem(new SampleFormPage())
|
||||
{
|
||||
Title = "Form Page Sample Command",
|
||||
Subtitle = "Define inputs to retrieve input from the user",
|
||||
},
|
||||
new ListItem(new SampleSettingsPage())
|
||||
{
|
||||
Title = "Sample settings page",
|
||||
Subtitle = "A demo of the settings helpers",
|
||||
},
|
||||
|
||||
new ListItem(new EvilSamplesPage())
|
||||
{
|
||||
Title = "Evil samples",
|
||||
|
||||
@@ -54,7 +54,7 @@ internal sealed partial class SpongeSettingsForm : Form
|
||||
|
||||
public override string DataJson() => throw new NotImplementedException();
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
public override string StateJson() => "{}";
|
||||
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
|
||||
@@ -50,7 +50,7 @@ internal sealed partial class YouTubeAPIForm : Form
|
||||
|
||||
public override string DataJson() => throw new NotImplementedException();
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
public override string StateJson() => "{}";
|
||||
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
|
||||
@@ -31,8 +31,8 @@ public partial class DetailsViewModel(IDetails _details, IPageContext context) :
|
||||
return;
|
||||
}
|
||||
|
||||
Title = model.Title;
|
||||
Body = model.Body;
|
||||
Title = model.Title ?? string.Empty;
|
||||
Body = model.Body ?? string.Empty;
|
||||
HeroImage = model.HeroImage;
|
||||
|
||||
UpdateProperty(nameof(Title));
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Text.Json;
|
||||
using AdaptiveCards.ObjectModel.WinUI3;
|
||||
using AdaptiveCards.Templating;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
using Windows.Data.Json;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class FormViewModel(IForm _form, IPageContext context) : ExtensionObjectViewModel(context)
|
||||
{
|
||||
private readonly ExtensionObject<IForm> _formModel = new(_form);
|
||||
|
||||
// Remember - "observable" properties from the model (via PropChanged)
|
||||
// cannot be marked [ObservableProperty]
|
||||
public string TemplateJson { get; protected set; } = "{}";
|
||||
|
||||
public string StateJson { get; protected set; } = "{}";
|
||||
|
||||
public string DataJson { get; protected set; } = "{}";
|
||||
|
||||
public AdaptiveCardParseResult? Card { get; private set; }
|
||||
|
||||
public override void InitializeProperties()
|
||||
{
|
||||
var model = _formModel.Unsafe;
|
||||
if (model == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
TemplateJson = model.TemplateJson();
|
||||
StateJson = model.StateJson();
|
||||
DataJson = model.DataJson();
|
||||
|
||||
AdaptiveCardTemplate template = new(TemplateJson);
|
||||
var cardJson = template.Expand(DataJson);
|
||||
Card = AdaptiveCard.FromJsonString(cardJson);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// If we fail to parse the card JSON, then display _our own card_
|
||||
// with the exception
|
||||
AdaptiveCardTemplate template = new(ErrorCardJson);
|
||||
|
||||
// todo: we could probably stick Card.Errrors in there too
|
||||
var dataJson = $$"""
|
||||
{
|
||||
"error_message": {{JsonSerializer.Serialize(e.Message)}},
|
||||
"error_stack": {{JsonSerializer.Serialize(e.StackTrace)}},
|
||||
"inner_exception": {{JsonSerializer.Serialize(e.InnerException?.Message)}},
|
||||
"template_json": {{JsonSerializer.Serialize(TemplateJson)}},
|
||||
"data_json": {{JsonSerializer.Serialize(DataJson)}}
|
||||
}
|
||||
""";
|
||||
var cardJson = template.Expand(dataJson);
|
||||
Card = AdaptiveCard.FromJsonString(cardJson);
|
||||
}
|
||||
|
||||
UpdateProperty(nameof(Card));
|
||||
}
|
||||
|
||||
public void HandleSubmit(IAdaptiveActionElement action, JsonObject inputs)
|
||||
{
|
||||
if (action is AdaptiveOpenUrlAction openUrlAction)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<LaunchUriMessage>(new(openUrlAction.Url));
|
||||
return;
|
||||
}
|
||||
|
||||
if (action is AdaptiveSubmitAction or AdaptiveExecuteAction)
|
||||
{
|
||||
// Get the data and inputs
|
||||
// var data = submitAction.DataJson.Stringify();
|
||||
var inputString = inputs.Stringify();
|
||||
|
||||
// _ = data;
|
||||
_ = inputString;
|
||||
|
||||
try
|
||||
{
|
||||
var model = _formModel.Unsafe!;
|
||||
if (model != null)
|
||||
{
|
||||
var result = model.SubmitForm(inputString);
|
||||
|
||||
// TODO Handle results
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PageContext.ShowException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly string ErrorCardJson = """
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "Error parsing form from extension",
|
||||
"wrap": true,
|
||||
"style": "heading",
|
||||
"size": "ExtraLarge",
|
||||
"weight": "Bolder",
|
||||
"color": "Attention"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"wrap": true,
|
||||
"text": "${error_message}",
|
||||
"color": "Attention"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"text": "${error_stack}",
|
||||
"fontType": "Monospace"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"wrap": true,
|
||||
"text": "Inner exception:"
|
||||
},
|
||||
{
|
||||
"type": "TextBlock",
|
||||
"wrap": true,
|
||||
"text": "${inner_exception}",
|
||||
"color": "Attention"
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class FormsPageViewModel : PageViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IFormPage> _model;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<FormViewModel> Forms { get; set; } = [];
|
||||
|
||||
// Remember - "observable" properties from the model (via PropChanged)
|
||||
// cannot be marked [ObservableProperty]
|
||||
public FormsPageViewModel(IFormPage model, TaskScheduler scheduler)
|
||||
: base(model, scheduler)
|
||||
{
|
||||
_model = new(model);
|
||||
}
|
||||
|
||||
//// Run on background thread, from InitializeAsync or Model_ItemsChanged
|
||||
private void FetchForms()
|
||||
{
|
||||
try
|
||||
{
|
||||
var newItems = _model.Unsafe!.Forms();
|
||||
|
||||
Forms.Clear();
|
||||
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
FormViewModel viewModel = new(item, this);
|
||||
viewModel.InitializeProperties();
|
||||
Forms.Add(viewModel);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowException(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeProperties()
|
||||
{
|
||||
base.InitializeProperties();
|
||||
|
||||
var listPage = _model.Unsafe;
|
||||
if (listPage == null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
FetchForms();
|
||||
}
|
||||
|
||||
protected override void FetchProperty(string propertyName)
|
||||
{
|
||||
base.FetchProperty(propertyName);
|
||||
|
||||
var model = this._model.Unsafe;
|
||||
if (model == null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
// Do we really not have any here?
|
||||
|
||||
// Should `Forms` be observable? That was what ended up footgunning widgets in DevHome, so :shurg:
|
||||
|
||||
// switch (propertyName)
|
||||
// {
|
||||
// case nameof(ShowDetails):
|
||||
// this.ShowDetails = model.ShowDetails;
|
||||
// break;
|
||||
// case nameof(PlaceholderText):
|
||||
// this.PlaceholderText = model.PlaceholderText;
|
||||
// break;
|
||||
// }
|
||||
UpdateProperty(propertyName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
public partial class MarkdownPageViewModel : PageViewModel
|
||||
{
|
||||
private readonly ExtensionObject<IMarkdownPage> _model;
|
||||
|
||||
public ObservableCollection<string> Bodies { get; set; } = [];
|
||||
|
||||
public List<CommandContextItemViewModel> Commands { get; private set; } = [];
|
||||
|
||||
public bool HasCommands => Commands.Count > 0;
|
||||
|
||||
public DetailsViewModel? Details { get; private set; }
|
||||
|
||||
[MemberNotNullWhen(true, nameof(Details))]
|
||||
public bool HasDetails => Details != null;
|
||||
|
||||
// Remember - "observable" properties from the model (via PropChanged)
|
||||
// cannot be marked [ObservableProperty]
|
||||
public MarkdownPageViewModel(IMarkdownPage model, TaskScheduler scheduler)
|
||||
: base(model, scheduler)
|
||||
{
|
||||
_model = new(model);
|
||||
}
|
||||
|
||||
//// Run on background thread, from InitializeAsync or Model_ItemsChanged
|
||||
private void FetchContent()
|
||||
{
|
||||
try
|
||||
{
|
||||
var newItems = _model.Unsafe!.Bodies();
|
||||
|
||||
Bodies.Clear();
|
||||
|
||||
foreach (var item in newItems)
|
||||
{
|
||||
Bodies.Add(item);
|
||||
}
|
||||
|
||||
UpdateProperty(nameof(HasDetails));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowException(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeProperties()
|
||||
{
|
||||
base.InitializeProperties();
|
||||
|
||||
var model = _model.Unsafe;
|
||||
if (model == null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
Commands = model.Commands
|
||||
.Where(contextItem => contextItem is ICommandContextItem)
|
||||
.Select(contextItem => (contextItem as ICommandContextItem)!)
|
||||
.Select(contextItem => new CommandContextItemViewModel(contextItem, PageContext))
|
||||
.ToList();
|
||||
|
||||
var extensionDetails = model.Details();
|
||||
if (extensionDetails != null)
|
||||
{
|
||||
Details = new(extensionDetails, PageContext);
|
||||
Details.InitializeProperties();
|
||||
UpdateDetails();
|
||||
}
|
||||
|
||||
FetchContent();
|
||||
}
|
||||
|
||||
protected override void FetchProperty(string propertyName)
|
||||
{
|
||||
base.FetchProperty(propertyName);
|
||||
|
||||
var model = this._model.Unsafe;
|
||||
if (model == null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
switch (propertyName)
|
||||
{
|
||||
// case nameof(Commands):
|
||||
// this.ShowDetails = model.ShowDetails;
|
||||
// break;
|
||||
case nameof(Details):
|
||||
var extensionDetails = model.Details();
|
||||
Details = extensionDetails != null ? new(extensionDetails, PageContext) : null;
|
||||
UpdateDetails();
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateProperty(propertyName);
|
||||
}
|
||||
|
||||
private void UpdateDetails()
|
||||
{
|
||||
UpdateProperty(nameof(Details));
|
||||
UpdateProperty(nameof(HasDetails));
|
||||
|
||||
Task.Factory.StartNew(
|
||||
() =>
|
||||
{
|
||||
if (HasDetails)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<ShowDetailsMessage>(new(Details));
|
||||
}
|
||||
else
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<HideDetailsMessage>();
|
||||
}
|
||||
},
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.None,
|
||||
PageContext.Scheduler);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Models;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
|
||||
public record LaunchUriMessage(Uri Uri)
|
||||
{
|
||||
}
|
||||
@@ -14,6 +14,9 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" />
|
||||
<PackageReference Include="AdaptiveCards.Templating" />
|
||||
<PackageReference Include="AdaptiveCards.ObjectModel.WinUI3" GeneratePathProperty="true" />
|
||||
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
|
||||
<cpcontrols:IconBox
|
||||
x:Name="IconBorder"
|
||||
Width="20"
|
||||
@@ -60,7 +59,7 @@
|
||||
Margin="12,0,0,0"
|
||||
x:Load="{x:Bind IsLoaded, Mode=OneWay}"
|
||||
CornerRadius="{StaticResource ControlCornerRadius}"
|
||||
SourceKey="{x:Bind ViewModel.CurrentPage.Icon, Mode=OneWay}"
|
||||
SourceKey="{x:Bind CurrentPageViewModel.Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
|
||||
<TextBlock
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using AdaptiveCards.Rendering.WinUI3;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed class AdaptiveCardsConfig
|
||||
{
|
||||
public static AdaptiveHostConfig Light { get; }
|
||||
|
||||
public static AdaptiveHostConfig Dark { get; }
|
||||
|
||||
static AdaptiveCardsConfig()
|
||||
{
|
||||
Light = AdaptiveHostConfig.FromJsonString(LightHostConfigString).HostConfig;
|
||||
Dark = AdaptiveHostConfig.FromJsonString(DarkHostConfigString).HostConfig;
|
||||
}
|
||||
|
||||
public static readonly string DarkHostConfigString = """
|
||||
{
|
||||
"spacing": {
|
||||
"small": 4,
|
||||
"default": 8,
|
||||
"medium": 20,
|
||||
"large": 30,
|
||||
"extraLarge": 40,
|
||||
"padding": 8
|
||||
},
|
||||
"separator": {
|
||||
"lineThickness": 0,
|
||||
"lineColor": "#C8FFFFFF"
|
||||
},
|
||||
"supportsInteractivity": true,
|
||||
"fontTypes": {
|
||||
"default": {
|
||||
"fontFamily": "'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
|
||||
"fontSizes": {
|
||||
"small": 12,
|
||||
"default": 12,
|
||||
"medium": 14,
|
||||
"large": 20,
|
||||
"extraLarge": 26
|
||||
},
|
||||
"fontWeights": {
|
||||
"lighter": 200,
|
||||
"default": 400,
|
||||
"bolder": 600
|
||||
}
|
||||
},
|
||||
"monospace": {
|
||||
"fontFamily": "'Courier New', Courier, monospace",
|
||||
"fontSizes": {
|
||||
"small": 12,
|
||||
"default": 12,
|
||||
"medium": 14,
|
||||
"large": 18,
|
||||
"extraLarge": 26
|
||||
},
|
||||
"fontWeights": {
|
||||
"lighter": 200,
|
||||
"default": 400,
|
||||
"bolder": 600
|
||||
}
|
||||
}
|
||||
},
|
||||
"containerStyles": {
|
||||
"default": {
|
||||
"backgroundColor": "#00000000",
|
||||
"borderColor": "#00000000",
|
||||
"foregroundColors": {
|
||||
"default": {
|
||||
"default": "#FFFFFF",
|
||||
"subtle": "#C8FFFFFF"
|
||||
},
|
||||
"accent": {
|
||||
"default": "#0063B1",
|
||||
"subtle": "#880063B1"
|
||||
},
|
||||
"attention": {
|
||||
"default": "#FF5555",
|
||||
"subtle": "#DDFF5555"
|
||||
},
|
||||
"good": {
|
||||
"default": "#54a254",
|
||||
"subtle": "#DD54a254"
|
||||
},
|
||||
"warning": {
|
||||
"default": "#c3ab23",
|
||||
"subtle": "#DDc3ab23"
|
||||
}
|
||||
}
|
||||
},
|
||||
"emphasis": {
|
||||
"backgroundColor": "#09FFFFFF",
|
||||
"borderColor": "#09FFFFFF",
|
||||
"foregroundColors": {
|
||||
"default": {
|
||||
"default": "#FFFFFF",
|
||||
"subtle": "#C8FFFFFF"
|
||||
},
|
||||
"accent": {
|
||||
"default": "#2E89FC",
|
||||
"subtle": "#882E89FC"
|
||||
},
|
||||
"attention": {
|
||||
"default": "#FF5555",
|
||||
"subtle": "#DDFF5555"
|
||||
},
|
||||
"good": {
|
||||
"default": "#54a254",
|
||||
"subtle": "#DD54a254"
|
||||
},
|
||||
"warning": {
|
||||
"default": "#c3ab23",
|
||||
"subtle": "#DDc3ab23"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imageSizes": {
|
||||
"small": 16,
|
||||
"medium": 24,
|
||||
"large": 32
|
||||
},
|
||||
"actions": {
|
||||
"maxActions": 5,
|
||||
"spacing": "default",
|
||||
"buttonSpacing": 8,
|
||||
"showCard": {
|
||||
"actionMode": "inline",
|
||||
"inlineTopMargin": 8
|
||||
},
|
||||
"actionsOrientation": "horizontal",
|
||||
"actionAlignment": "stretch"
|
||||
},
|
||||
"adaptiveCard": {
|
||||
"allowCustomStyle": false
|
||||
},
|
||||
"imageSet": {
|
||||
"imageSize": "medium",
|
||||
"maxImageHeight": 100
|
||||
},
|
||||
"factSet": {
|
||||
"title": {
|
||||
"color": "default",
|
||||
"size": "default",
|
||||
"isSubtle": false,
|
||||
"weight": "bolder",
|
||||
"wrap": true,
|
||||
"maxWidth": 150
|
||||
},
|
||||
"value": {
|
||||
"color": "default",
|
||||
"size": "default",
|
||||
"isSubtle": false,
|
||||
"weight": "default",
|
||||
"wrap": true
|
||||
},
|
||||
"spacing": 8
|
||||
},
|
||||
"textStyles": {
|
||||
"heading": {
|
||||
"size": "large",
|
||||
"weight": "bolder",
|
||||
"color": "default",
|
||||
"isSubtle": false,
|
||||
"fontType": "default"
|
||||
},
|
||||
"columnHeader": {
|
||||
"size": "medium",
|
||||
"weight": "bolder",
|
||||
"color": "default",
|
||||
"isSubtle": false,
|
||||
"fontType": "default"
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
public static readonly string LightHostConfigString = """
|
||||
{
|
||||
"spacing": {
|
||||
"small": 4,
|
||||
"default": 8,
|
||||
"medium": 20,
|
||||
"large": 30,
|
||||
"extraLarge": 40,
|
||||
"padding": 8
|
||||
},
|
||||
"separator": {
|
||||
"lineThickness": 0,
|
||||
"lineColor": "#606060"
|
||||
},
|
||||
"supportsInteractivity": true,
|
||||
"fontTypes": {
|
||||
"default": {
|
||||
"fontFamily": "'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
|
||||
"fontSizes": {
|
||||
"small": 12,
|
||||
"default": 12,
|
||||
"medium": 14,
|
||||
"large": 20,
|
||||
"extraLarge": 26
|
||||
},
|
||||
"fontWeights": {
|
||||
"lighter": 200,
|
||||
"default": 400,
|
||||
"bolder": 600
|
||||
}
|
||||
},
|
||||
"monospace": {
|
||||
"fontFamily": "'Courier New', Courier, monospace",
|
||||
"fontSizes": {
|
||||
"small": 12,
|
||||
"default": 12,
|
||||
"medium": 14,
|
||||
"large": 18,
|
||||
"extraLarge": 26
|
||||
},
|
||||
"fontWeights": {
|
||||
"lighter": 200,
|
||||
"default": 400,
|
||||
"bolder": 600
|
||||
}
|
||||
}
|
||||
},
|
||||
"containerStyles": {
|
||||
"default": {
|
||||
"backgroundColor": "#00000000",
|
||||
"borderColor": "#00000000",
|
||||
"foregroundColors": {
|
||||
"default": {
|
||||
"default": "#E6000000",
|
||||
"subtle": "#99000000"
|
||||
},
|
||||
"accent": {
|
||||
"default": "#0063B1",
|
||||
"subtle": "#880063B1"
|
||||
},
|
||||
"attention": {
|
||||
"default": "#C00000",
|
||||
"subtle": "#DDC00000"
|
||||
},
|
||||
"good": {
|
||||
"default": "#54a254",
|
||||
"subtle": "#DD54a254"
|
||||
},
|
||||
"warning": {
|
||||
"default": "#c3ab23",
|
||||
"subtle": "#DDc3ab23"
|
||||
}
|
||||
}
|
||||
},
|
||||
"emphasis": {
|
||||
"backgroundColor": "#80F6F6F6",
|
||||
"borderColor": "#80F6F6F6",
|
||||
"foregroundColors": {
|
||||
"default": {
|
||||
"default": "#E6000000",
|
||||
"subtle": "#99000000"
|
||||
},
|
||||
"accent": {
|
||||
"default": "#2E89FC",
|
||||
"subtle": "#882E89FC"
|
||||
},
|
||||
"attention": {
|
||||
"default": "#C00000",
|
||||
"subtle": "#DDC00000"
|
||||
},
|
||||
"good": {
|
||||
"default": "#54a254",
|
||||
"subtle": "#DD54a254"
|
||||
},
|
||||
"warning": {
|
||||
"default": "#c3ab23",
|
||||
"subtle": "#DDc3ab23"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"imageSizes": {
|
||||
"small": 16,
|
||||
"medium": 24,
|
||||
"large": 32
|
||||
},
|
||||
"actions": {
|
||||
"maxActions": 5,
|
||||
"spacing": "default",
|
||||
"buttonSpacing": 8,
|
||||
"showCard": {
|
||||
"actionMode": "inline",
|
||||
"inlineTopMargin": 8
|
||||
},
|
||||
"actionsOrientation": "horizontal",
|
||||
"actionAlignment": "stretch"
|
||||
},
|
||||
"adaptiveCard": {
|
||||
"allowCustomStyle": false
|
||||
},
|
||||
"imageSet": {
|
||||
"imageSize": "medium",
|
||||
"maxImageHeight": 100
|
||||
},
|
||||
"factSet": {
|
||||
"title": {
|
||||
"color": "default",
|
||||
"size": "default",
|
||||
"isSubtle": false,
|
||||
"weight": "bolder",
|
||||
"wrap": true,
|
||||
"maxWidth": 150
|
||||
},
|
||||
"value": {
|
||||
"color": "default",
|
||||
"size": "default",
|
||||
"isSubtle": false,
|
||||
"weight": "default",
|
||||
"wrap": true
|
||||
},
|
||||
"spacing": 8
|
||||
},
|
||||
"textStyles": {
|
||||
"heading": {
|
||||
"size": "large",
|
||||
"weight": "bolder",
|
||||
"color": "default",
|
||||
"isSubtle": false,
|
||||
"fontType": "default"
|
||||
},
|
||||
"columnHeader": {
|
||||
"size": "medium",
|
||||
"weight": "bolder",
|
||||
"color": "default",
|
||||
"isSubtle": false,
|
||||
"fontType": "default"
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Microsoft.CmdPal.UI.Controls.FormControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:viewmodels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<converters:StringVisibilityConverter
|
||||
x:Key="StringNotEmptyToVisibilityConverter"
|
||||
EmptyValue="Collapsed"
|
||||
NotEmptyValue="Visible" />
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid
|
||||
x:Name="ContentGrid"
|
||||
Padding="12, 8, 8, 8"
|
||||
CornerRadius="{StaticResource OverlayCornerRadius}"
|
||||
Margin="0,4,4,4"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
|
||||
BorderThickness="1, 1, 1, 2">
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using AdaptiveCards.ObjectModel.WinUI3;
|
||||
using AdaptiveCards.Rendering.WinUI3;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Controls;
|
||||
|
||||
public sealed partial class FormControl : UserControl
|
||||
{
|
||||
private static readonly AdaptiveCardRenderer _renderer;
|
||||
private FormViewModel? _viewModel;
|
||||
|
||||
public FormViewModel? ViewModel { get => _viewModel; set => AttachViewModel(value); }
|
||||
|
||||
static FormControl()
|
||||
{
|
||||
_renderer = new AdaptiveCardRenderer();
|
||||
}
|
||||
|
||||
public FormControl()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
var lightTheme = ActualTheme == Microsoft.UI.Xaml.ElementTheme.Light;
|
||||
_renderer.HostConfig = lightTheme ? AdaptiveCardsConfig.Light : AdaptiveCardsConfig.Dark;
|
||||
|
||||
// TODO in the future, we should handle ActualThemeChanged and replace
|
||||
// our rendered card with one for that theme. But today is not that day
|
||||
}
|
||||
|
||||
private void AttachViewModel(FormViewModel? vm)
|
||||
{
|
||||
if (_viewModel != null)
|
||||
{
|
||||
_viewModel.PropertyChanged -= ViewModel_PropertyChanged;
|
||||
}
|
||||
|
||||
_viewModel = vm;
|
||||
|
||||
if (_viewModel != null)
|
||||
{
|
||||
_viewModel.PropertyChanged += ViewModel_PropertyChanged;
|
||||
|
||||
var c = _viewModel.Card;
|
||||
if (c != null)
|
||||
{
|
||||
DisplayCard(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.PropertyName == nameof(ViewModel.Card))
|
||||
{
|
||||
var c = ViewModel.Card;
|
||||
if (c != null)
|
||||
{
|
||||
DisplayCard(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayCard(AdaptiveCardParseResult result)
|
||||
{
|
||||
var rendered = _renderer.RenderAdaptiveCard(result.AdaptiveCard);
|
||||
rendered.Action += Rendered_Action;
|
||||
ContentGrid.Children.Clear();
|
||||
ContentGrid.Children.Add(rendered.FrameworkElement);
|
||||
}
|
||||
|
||||
private void Rendered_Action(RenderedAdaptiveCard sender, AdaptiveActionEventArgs args) =>
|
||||
ViewModel?.HandleSubmit(args.Action, args.Inputs.AsJson());
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Microsoft.CmdPal.UI.FormsPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:Interactions="using:Microsoft.Xaml.Interactions.Core"
|
||||
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI"
|
||||
xmlns:cmdPalControls="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewmodels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<ProgressBar
|
||||
IsIndeterminate="True"
|
||||
VerticalAlignment="Top"
|
||||
Visibility="{x:Bind ViewModel.IsLoading, Mode=OneWay}" />
|
||||
|
||||
<controls:SwitchPresenter TargetType="local:ViewModelLoadedState" Value="{x:Bind LoadedState, Mode=OneWay}">
|
||||
<controls:Case Value="Loaded">
|
||||
<ScrollView VerticalAlignment="Top" VerticalScrollMode="Enabled">
|
||||
<ItemsControl VerticalAlignment="Stretch" Margin="8" ItemsSource="{x:Bind ViewModel.Forms, Mode=OneWay}">
|
||||
<!-- or ListView or ItemsRepeater more likely... -->
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="viewmodels:FormViewModel">
|
||||
<cmdPalControls:FormControl ViewModel="{x:Bind}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollView>
|
||||
|
||||
</controls:Case>
|
||||
|
||||
<controls:Case IsDefault="True" Value="Loading">
|
||||
</controls:Case>
|
||||
|
||||
<controls:Case Value="Error">
|
||||
<StackPanel Orientation="Vertical" Margin="16">
|
||||
<TextBlock Text="Error on page" FontSize="18" Foreground="{ThemeResource SystemErrorTextColor}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.ErrorMessage, Mode=OneWay}" IsTextSelectionEnabled="True"/>
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using CommunityToolkit.Common;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class FormsPage : Page
|
||||
{
|
||||
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
public FormsPageViewModel? ViewModel
|
||||
{
|
||||
get => (FormsPageViewModel?)GetValue(ViewModelProperty);
|
||||
set => SetValue(ViewModelProperty, value);
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for ViewModel. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty ViewModelProperty =
|
||||
DependencyProperty.Register(nameof(ViewModel), typeof(FormsPageViewModel), typeof(FormsPage), new PropertyMetadata(null));
|
||||
|
||||
public ViewModelLoadedState LoadedState
|
||||
{
|
||||
get => (ViewModelLoadedState)GetValue(LoadedStateProperty);
|
||||
set => SetValue(LoadedStateProperty, value);
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for LoadedState. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty LoadedStateProperty =
|
||||
DependencyProperty.Register(nameof(LoadedState), typeof(ViewModelLoadedState), typeof(FormsPage), new PropertyMetadata(ViewModelLoadedState.Loading));
|
||||
|
||||
public FormsPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
LoadedState = ViewModelLoadedState.Loading;
|
||||
if (e.Parameter is FormsPageViewModel fpvm)
|
||||
{
|
||||
if (!fpvm.IsInitialized
|
||||
&& fpvm.InitializeCommand != null)
|
||||
{
|
||||
ViewModel = null;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
// You know, this creates the situation where we wait for
|
||||
// both loading page properties, AND the items, before we
|
||||
// display anything.
|
||||
//
|
||||
// We almost need to do an async await on initialize, then
|
||||
// just a fire-and-forget on FetchItems.
|
||||
fpvm.InitializeCommand.Execute(null);
|
||||
|
||||
await fpvm.InitializeCommand.ExecutionTask!;
|
||||
|
||||
if (fpvm.InitializeCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
// TODO: Handle failure case
|
||||
System.Diagnostics.Debug.WriteLine(fpvm.InitializeCommand.ExecutionTask.Exception);
|
||||
|
||||
_ = _queue.EnqueueAsync(() =>
|
||||
{
|
||||
LoadedState = ViewModelLoadedState.Error;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = _queue.EnqueueAsync(() =>
|
||||
{
|
||||
var result = (bool)fpvm.InitializeCommand.ExecutionTask.GetResultOrDefault()!;
|
||||
|
||||
ViewModel = fpvm;
|
||||
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(result ? fpvm : null));
|
||||
LoadedState = result ? ViewModelLoadedState.Loaded : ViewModelLoadedState.Error;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewModel = fpvm;
|
||||
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(fpvm));
|
||||
LoadedState = ViewModelLoadedState.Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
base.OnNavigatedTo(e);
|
||||
}
|
||||
|
||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) => base.OnNavigatingFrom(e);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<Page
|
||||
x:Class="Microsoft.CmdPal.UI.MarkdownPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:Interactions="using:Microsoft.Xaml.Interactions.Core"
|
||||
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:Microsoft.CmdPal.UI"
|
||||
xmlns:cmdPalControls="using:Microsoft.CmdPal.UI.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:viewmodels="using:Microsoft.CmdPal.UI.ViewModels"
|
||||
xmlns:labs="using:CommunityToolkit.Labs.WinUI.MarkdownTextBlock"
|
||||
Background="Transparent"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid>
|
||||
<ProgressBar
|
||||
IsIndeterminate="True"
|
||||
VerticalAlignment="Top"
|
||||
Visibility="{x:Bind ViewModel.IsLoading, Mode=OneWay}" />
|
||||
|
||||
<controls:SwitchPresenter TargetType="local:ViewModelLoadedState" Value="{x:Bind LoadedState, Mode=OneWay}">
|
||||
<controls:Case Value="Loaded">
|
||||
<ScrollView VerticalAlignment="Top" VerticalScrollMode="Enabled">
|
||||
<ItemsControl VerticalAlignment="Stretch" Margin="8" ItemsSource="{x:Bind ViewModel.Bodies, Mode=OneWay}">
|
||||
<!-- or ListView or ItemsRepeater more likely... -->
|
||||
<ItemsControl.ItemTemplate>
|
||||
|
||||
<DataTemplate x:DataType="x:String">
|
||||
|
||||
<Grid
|
||||
x:Name="ContentGrid"
|
||||
Padding="12, 8, 8, 8"
|
||||
CornerRadius="{StaticResource OverlayCornerRadius}"
|
||||
Margin="0,4,4,4"
|
||||
BorderThickness="1, 1, 1, 2"
|
||||
Background="{ThemeResource CardBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}">
|
||||
|
||||
<labs:MarkdownTextBlock
|
||||
Text="{x:Bind}"
|
||||
Config="{x:Bind local:MarkdownPage.MarkdownConfig}"
|
||||
Background="Transparent" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</ScrollView>
|
||||
|
||||
</controls:Case>
|
||||
|
||||
<controls:Case IsDefault="True" Value="Loading">
|
||||
<TextBlock Text="I am a loading form page" />
|
||||
</controls:Case>
|
||||
|
||||
<controls:Case Value="Error">
|
||||
<StackPanel Orientation="Vertical" Margin="16">
|
||||
<TextBlock Text="I am an error form page" />
|
||||
<TextBlock Text="Error on page" FontSize="18" Foreground="{ThemeResource SystemErrorTextColor}" />
|
||||
<TextBlock Text="{x:Bind ViewModel.ErrorMessage, Mode=OneWay}" IsTextSelectionEnabled="True"/>
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
</controls:SwitchPresenter>
|
||||
</Grid>
|
||||
</Page>
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using CommunityToolkit.Common;
|
||||
using CommunityToolkit.Labs.WinUI.MarkdownTextBlock;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
/// <summary>
|
||||
/// An empty page that can be used on its own or navigated to within a Frame.
|
||||
/// </summary>
|
||||
public sealed partial class MarkdownPage : Page
|
||||
{
|
||||
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
|
||||
|
||||
public static readonly MarkdownConfig MarkdownConfig = CommunityToolkit.Labs.WinUI.MarkdownTextBlock.MarkdownConfig.Default;
|
||||
|
||||
public MarkdownPageViewModel? ViewModel
|
||||
{
|
||||
get => (MarkdownPageViewModel?)GetValue(ViewModelProperty);
|
||||
set => SetValue(ViewModelProperty, value);
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for ViewModel. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty ViewModelProperty =
|
||||
DependencyProperty.Register(nameof(ViewModel), typeof(MarkdownPageViewModel), typeof(FormsPage), new PropertyMetadata(null));
|
||||
|
||||
public ViewModelLoadedState LoadedState
|
||||
{
|
||||
get => (ViewModelLoadedState)GetValue(LoadedStateProperty);
|
||||
set => SetValue(LoadedStateProperty, value);
|
||||
}
|
||||
|
||||
// Using a DependencyProperty as the backing store for LoadedState. This enables animation, styling, binding, etc...
|
||||
public static readonly DependencyProperty LoadedStateProperty =
|
||||
DependencyProperty.Register(nameof(LoadedState), typeof(ViewModelLoadedState), typeof(FormsPage), new PropertyMetadata(ViewModelLoadedState.Loading));
|
||||
|
||||
public MarkdownPage()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
protected override void OnNavigatedTo(NavigationEventArgs e)
|
||||
{
|
||||
LoadedState = ViewModelLoadedState.Loading;
|
||||
if (e.Parameter is MarkdownPageViewModel mdpvm)
|
||||
{
|
||||
if (!mdpvm.IsInitialized
|
||||
&& mdpvm.InitializeCommand != null)
|
||||
{
|
||||
ViewModel = null;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
// You know, this creates the situation where we wait for
|
||||
// both loading page properties, AND the items, before we
|
||||
// display anything.
|
||||
//
|
||||
// We almost need to do an async await on initialize, then
|
||||
// just a fire-and-forget on FetchItems.
|
||||
mdpvm.InitializeCommand.Execute(null);
|
||||
|
||||
await mdpvm.InitializeCommand.ExecutionTask!;
|
||||
|
||||
if (mdpvm.InitializeCommand.ExecutionTask.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
// TODO: Handle failure case
|
||||
System.Diagnostics.Debug.WriteLine(mdpvm.InitializeCommand.ExecutionTask.Exception);
|
||||
|
||||
_ = _queue.EnqueueAsync(() =>
|
||||
{
|
||||
LoadedState = ViewModelLoadedState.Error;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = _queue.EnqueueAsync(() =>
|
||||
{
|
||||
var result = (bool)mdpvm.InitializeCommand.ExecutionTask.GetResultOrDefault()!;
|
||||
|
||||
ViewModel = mdpvm;
|
||||
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(result ? mdpvm : null));
|
||||
LoadedState = result ? ViewModelLoadedState.Loaded : ViewModelLoadedState.Error;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewModel = mdpvm;
|
||||
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(mdpvm));
|
||||
LoadedState = ViewModelLoadedState.Loaded;
|
||||
}
|
||||
}
|
||||
|
||||
base.OnNavigatedTo(e);
|
||||
}
|
||||
|
||||
protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) => base.OnNavigatingFrom(e);
|
||||
}
|
||||
@@ -100,6 +100,12 @@
|
||||
<Private>True</Private>
|
||||
<CopyLocalSatelliteAssemblies>True</CopyLocalSatelliteAssemblies>
|
||||
</ProjectReference>
|
||||
|
||||
<!-- LOAD BEARING: GeneratePathProperty=true on BOTH the AC dependencies. Don't forget the AdaptiveCardsWorkaround below -->
|
||||
<PackageReference Include="AdaptiveCards.ObjectModel.WinUI3" GeneratePathProperty="true" />
|
||||
<PackageReference Include="AdaptiveCards.Rendering.WinUI3" GeneratePathProperty="True" />
|
||||
<PackageReference Include="AdaptiveCards.Templating" />
|
||||
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -134,4 +140,23 @@
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<!-- <AdaptiveCardsWorkaround> -->
|
||||
<!-- Workaround for Adaptive Cards not supporting correct RIDs when using .NET 8 -->
|
||||
<PropertyGroup>
|
||||
<AdaptiveCardsNative>runtimes\win10-$(Platform)\native</AdaptiveCardsNative>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Content
|
||||
Include="$(PkgAdaptiveCards_ObjectModel_WinUI3)\$(AdaptiveCardsNative)\AdaptiveCards.ObjectModel.WinUI3.dll"
|
||||
Link="AdaptiveCards.ObjectModel.WinUI3.dll"
|
||||
CopyToOutputDirectory="PreserveNewest" />
|
||||
<Content
|
||||
Include="$(PkgAdaptiveCards_Rendering_WinUI3)\$(AdaptiveCardsNative)\AdaptiveCards.Rendering.WinUI3.dll"
|
||||
Link="AdaptiveCards.Rendering.WinUI3.dll"
|
||||
CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
<!-- </AdaptiveCardsWorkaround> -->
|
||||
|
||||
</Project>
|
||||
@@ -10,6 +10,7 @@ using Microsoft.CmdPal.UI.ViewModels.Messages;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.UI;
|
||||
|
||||
@@ -22,7 +23,8 @@ public sealed partial class ShellPage :
|
||||
IRecipient<NavigateToDetailsMessage>,
|
||||
IRecipient<PerformCommandMessage>,
|
||||
IRecipient<ShowDetailsMessage>,
|
||||
IRecipient<HideDetailsMessage>
|
||||
IRecipient<HideDetailsMessage>,
|
||||
IRecipient<LaunchUriMessage>
|
||||
{
|
||||
private readonly DrillInNavigationTransitionInfo _drillInNavigationTransitionInfo = new();
|
||||
|
||||
@@ -44,6 +46,8 @@ public sealed partial class ShellPage :
|
||||
WeakReferenceMessenger.Default.Register<ShowDetailsMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<HideDetailsMessage>(this);
|
||||
|
||||
WeakReferenceMessenger.Default.Register<LaunchUriMessage>(this);
|
||||
|
||||
RootFrame.Navigate(typeof(LoadingPage), ViewModel);
|
||||
}
|
||||
|
||||
@@ -53,6 +57,8 @@ public sealed partial class ShellPage :
|
||||
{
|
||||
if (RootFrame.CanGoBack)
|
||||
{
|
||||
HideDetails();
|
||||
|
||||
RootFrame.GoBack();
|
||||
HideDetails();
|
||||
RootFrame.ForwardStack.Clear();
|
||||
@@ -98,11 +104,34 @@ public sealed partial class ShellPage :
|
||||
ViewModel.CurrentPage = pageViewModel;
|
||||
});
|
||||
}
|
||||
|
||||
// else if markdown, forms, TODO
|
||||
else if (command is IFormPage formsPage)
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
// Also hide our details pane about here, if we had one
|
||||
HideDetails();
|
||||
var pageViewModel = new FormsPageViewModel(formsPage, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
RootFrame.Navigate(typeof(FormsPage), pageViewModel, _slideRightTransition);
|
||||
SearchBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
|
||||
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(pageViewModel));
|
||||
});
|
||||
}
|
||||
else if (command is IMarkdownPage markdownPage)
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
// Also hide our details pane about here, if we had one
|
||||
HideDetails();
|
||||
var pageViewModel = new MarkdownPageViewModel(markdownPage, TaskScheduler.FromCurrentSynchronizationContext());
|
||||
RootFrame.Navigate(typeof(MarkdownPage), pageViewModel, _slideRightTransition);
|
||||
SearchBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
|
||||
WeakReferenceMessenger.Default.Send<NavigateToPageMessage>(new(pageViewModel));
|
||||
});
|
||||
}
|
||||
else if (command is IInvokableCommand invokable)
|
||||
{
|
||||
invokable.Invoke();
|
||||
// TODO Handle results
|
||||
_ = invokable.Invoke();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -120,4 +149,6 @@ public sealed partial class ShellPage :
|
||||
public void Receive(HideDetailsMessage message) => HideDetails();
|
||||
|
||||
private void HideDetails() => ViewModel.IsDetailsVisible = false;
|
||||
|
||||
public void Receive(LaunchUriMessage message) => _ = Launcher.LaunchUriAsync(message.Uri);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
namespace Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
public class Form : IForm
|
||||
public abstract class Form : IForm
|
||||
{
|
||||
public string Data { get; set; } = string.Empty;
|
||||
public virtual string Data { get; set; } = string.Empty;
|
||||
|
||||
public string State { get; set; } = string.Empty;
|
||||
public virtual string State { get; set; } = string.Empty;
|
||||
|
||||
public string Template { get; set; } = string.Empty;
|
||||
public virtual string Template { get; set; } = string.Empty;
|
||||
|
||||
public virtual string DataJson() => Data;
|
||||
|
||||
@@ -18,5 +18,5 @@ public class Form : IForm
|
||||
|
||||
public virtual string TemplateJson() => Template;
|
||||
|
||||
public virtual ICommandResult SubmitForm(string payload) => throw new NotImplementedException();
|
||||
public abstract ICommandResult SubmitForm(string payload);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
public partial class FormPage : Page, IFormPage
|
||||
public abstract partial class FormPage : Page, IFormPage
|
||||
{
|
||||
public virtual IForm[] Forms() => throw new NotImplementedException();
|
||||
public abstract IForm[] Forms();
|
||||
}
|
||||
|
||||
@@ -6,23 +6,21 @@ namespace Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
public class MarkdownPage : Page, IMarkdownPage
|
||||
{
|
||||
private ITag[] _tags = [];
|
||||
private IDetails? _details;
|
||||
|
||||
public ITag[] Tags
|
||||
public IDetails? Details
|
||||
{
|
||||
get => _tags;
|
||||
get => _details;
|
||||
set
|
||||
{
|
||||
_tags = value;
|
||||
OnPropertyChanged(nameof(Tags));
|
||||
_details = value;
|
||||
OnPropertyChanged(nameof(Details));
|
||||
}
|
||||
}
|
||||
|
||||
public IContextItem[] Commands { get; set; } = [];
|
||||
|
||||
public virtual string[] Bodies() => throw new NotImplementedException();
|
||||
public virtual string[] Bodies() => [];
|
||||
|
||||
public virtual IDetails Details() => throw new NotImplementedException();
|
||||
|
||||
// public IDetails Details { get => _Details; set { _Details = value; OnPropertyChanged(nameof(Details)); } }
|
||||
IDetails? IMarkdownPage.Details() => Details;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SampleMarkdownDetails : MarkdownPage
|
||||
{
|
||||
public SampleMarkdownDetails()
|
||||
{
|
||||
Icon = new(string.Empty);
|
||||
Name = "Markdown with Details";
|
||||
Details = new Details()
|
||||
{
|
||||
Body = "... with _even more Markdown_ by it.",
|
||||
};
|
||||
}
|
||||
|
||||
public override string[] Bodies() => [
|
||||
"""
|
||||
# This page also has details
|
||||
|
||||
So you can have markdown...
|
||||
""",
|
||||
"""
|
||||
But what this is really useful for is the tags and other things you can put into
|
||||
Details. Which I'd do. **IF I HAD ANY**.
|
||||
"""
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace SamplePagesExtension;
|
||||
|
||||
internal sealed partial class SampleMarkdownManyBodies : MarkdownPage
|
||||
{
|
||||
public SampleMarkdownManyBodies()
|
||||
{
|
||||
Icon = new(string.Empty);
|
||||
Name = "Markdown with many bodies";
|
||||
}
|
||||
|
||||
public override string[] Bodies() => [
|
||||
"""
|
||||
# This page has many bodies
|
||||
|
||||
On it you'll find multiple blocks of markdown content
|
||||
""",
|
||||
"""
|
||||
## Here's another block
|
||||
|
||||
_Maybe_ you could use this pattern for implementing a post with comments page.
|
||||
""",
|
||||
"""
|
||||
> or don't, it's your app, do whatever you want
|
||||
"""
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user