Compare commits
32 Commits
jay/DarkMo
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5249a0ba7 | ||
|
|
8dca07767c | ||
|
|
9ec1eae62e | ||
|
|
a5b09f7c34 | ||
|
|
81f5032eef | ||
|
|
5f8dd34d02 | ||
|
|
9380f83e82 | ||
|
|
1493b52f82 | ||
|
|
b86bd28be5 | ||
|
|
dfcf309135 | ||
|
|
53d443f520 | ||
|
|
c617055888 | ||
|
|
8e72cfd9d0 | ||
|
|
3808659b76 | ||
|
|
5127860759 | ||
|
|
5145a5302e | ||
|
|
57f86edd9e | ||
|
|
6971ed8845 | ||
|
|
d31fe2f390 | ||
|
|
000e443fec | ||
|
|
dcbd90206e | ||
|
|
caa2ae5a06 | ||
|
|
2cb58ae087 | ||
|
|
87f65e90d1 | ||
|
|
a285d32697 | ||
|
|
1045c4b830 | ||
|
|
b4f9e7b32e | ||
|
|
fc866d3a6a | ||
|
|
a02da42722 | ||
|
|
47d9563623 | ||
|
|
7ab131c467 | ||
|
|
b8b6418e68 |
@@ -736,6 +736,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ManagedCsWin32", "src\commo
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerRenameUITest", "src\modules\powerrename\PowerRenameUITest\PowerRenameUITest.csproj", "{9D3F3793-EFE3-4525-8782-238015DABA62}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Actions", "src\modules\cmdpal\ext\MIcrosoft.CmdPal.Ext.Actions\Microsoft.CmdPal.Ext.Actions.csproj", "{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Core.ViewModels", "src\modules\cmdpal\Microsoft.CmdPal.Core.ViewModels\Microsoft.CmdPal.Core.ViewModels.csproj", "{24133F7F-C1D1-DE04-EFA8-F5D5467FE027}"
|
||||
@@ -2740,6 +2742,14 @@ Global
|
||||
{9D3F3793-EFE3-4525-8782-238015DABA62}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{9D3F3793-EFE3-4525-8782-238015DABA62}.Release|x64.ActiveCfg = Release|x64
|
||||
{9D3F3793-EFE3-4525-8782-238015DABA62}.Release|x64.Build.0 = Release|x64
|
||||
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Debug|x64.Build.0 = Debug|x64
|
||||
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Release|x64.ActiveCfg = Release|x64
|
||||
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80}.Release|x64.Build.0 = Release|x64
|
||||
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{840455DF-5634-51BB-D937-9D7D32F0B0C2}.Debug|x64.ActiveCfg = Debug|x64
|
||||
@@ -3035,6 +3045,7 @@ Global
|
||||
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {3846508C-77EB-4034-A702-F8BB263C4F79}
|
||||
{24133F7F-C1D1-DE04-EFA8-F5D5467FE027} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{9D3F3793-EFE3-4525-8782-238015DABA62} = {89E20BCE-EB9C-46C8-8B50-E01A82E6FDC3}
|
||||
{0A4D5CD7-C03D-63C6-E1C5-A8CB1BB4FD80} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
|
||||
{840455DF-5634-51BB-D937-9D7D32F0B0C2} = {7520A2FE-00A2-49B8-83ED-DB216E874C04}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
@@ -26,6 +26,11 @@
|
||||
"input": "pushd .\\ExtensionTemplate\\ ; git archive -o ..\\Microsoft.CmdPal.UI.ViewModels\\Assets\\template.zip HEAD -- .\\TemplateCmdPalExtension\\ ; popd",
|
||||
"name": "Update template project",
|
||||
"description": "zips up the ExtensionTemplate into our assets. Run this in the cmdpal/ directory."
|
||||
},
|
||||
{
|
||||
"input": "pwsh -c .\\clean-sdk.ps1",
|
||||
"name": "Delete old extensions output",
|
||||
"description": "Delete old extensions output directory.\r\nUse this after regenerating the interface, otherwise it will not pass fast up to date checks."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// 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.Mvvm.Messaging;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public partial class ArgumentItemViewModel : ExtensionObjectViewModel
|
||||
{
|
||||
public ExtensionObject<ICommandArgument> Model => _model;
|
||||
|
||||
private readonly ExtensionObject<ICommandArgument> _model = new(null);
|
||||
|
||||
public ParameterType Type { get; private set; }
|
||||
|
||||
public string Name { get; private set; } = string.Empty;
|
||||
|
||||
public bool Required { get; private set; }
|
||||
|
||||
private string ModelDisplayName { get; set; } = string.Empty;
|
||||
|
||||
public string DisplayName => string.IsNullOrEmpty(ModelDisplayName) ? Name : ModelDisplayName;
|
||||
|
||||
// TODO! This should be an ExtensionObject<object> since it's out-of-proc
|
||||
public object? Value
|
||||
{
|
||||
get; set
|
||||
{
|
||||
field = value;
|
||||
SafeSetValue(value);
|
||||
}
|
||||
}
|
||||
|
||||
public ArgumentItemViewModel(ExtensionObject<ICommandArgument> model, WeakReference<IPageContext> pageContext)
|
||||
: base(pageContext)
|
||||
{
|
||||
_model = model;
|
||||
}
|
||||
|
||||
public override void InitializeProperties()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Type = model.Type;
|
||||
Name = model.Name;
|
||||
Required = model.Required;
|
||||
Value = model.Value;
|
||||
ModelDisplayName = model.DisplayName;
|
||||
|
||||
// Register for property changes
|
||||
model.PropChanged += Model_PropChanged;
|
||||
}
|
||||
|
||||
private void Model_PropChanged(object sender, IPropChangedEventArgs args)
|
||||
{
|
||||
try
|
||||
{
|
||||
FetchProperty(args.PropertyName);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void FetchProperty(string propertyName)
|
||||
{
|
||||
var model = this._model.Unsafe;
|
||||
if (model == null)
|
||||
{
|
||||
return; // throw?
|
||||
}
|
||||
|
||||
switch (propertyName)
|
||||
{
|
||||
case nameof(ICommandArgument.Type):
|
||||
if (model.Type == Type)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Type = model.Type;
|
||||
break;
|
||||
case nameof(ICommandArgument.Name):
|
||||
if (model.Name == Name)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Name = model.Name;
|
||||
UpdateProperty(nameof(DisplayName));
|
||||
break;
|
||||
case nameof(ICommandArgument.Required):
|
||||
if (model.Required == Required)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Required = model.Required;
|
||||
break;
|
||||
case nameof(ICommandArgument.Value):
|
||||
if (model.Value == Value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Value = model.Value;
|
||||
break;
|
||||
case nameof(ICommandArgument.DisplayName):
|
||||
if (model.DisplayName == ModelDisplayName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ModelDisplayName = model.DisplayName;
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateProperty(propertyName);
|
||||
}
|
||||
|
||||
private void SafeSetValue(object? value)
|
||||
{
|
||||
_ = Task.Run(() => SafeSetValueSynchronous(value));
|
||||
}
|
||||
|
||||
private void SafeSetValueSynchronous(object? value)
|
||||
{
|
||||
try
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
model.Value = value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenPicker()
|
||||
{
|
||||
var model = _model.Unsafe;
|
||||
if (model == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WeakReferenceMessenger.Default.Send<RequestOpenPickerMessage>(new(_model));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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.ComponentModel;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels;
|
||||
|
||||
public partial class ArgumentsViewModel : INotifyPropertyChanged
|
||||
{
|
||||
public ObservableCollection<ArgumentItemViewModel> Arguments { get; } = new();
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
|
||||
{
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||
}
|
||||
}
|
||||
@@ -149,12 +149,12 @@ public partial class CommandBarViewModel : ObservableObject,
|
||||
|
||||
if (command.HasMoreCommands)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command));
|
||||
return ContextKeybindingResult.KeepOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command));
|
||||
return ContextKeybindingResult.Hide;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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 Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
@@ -62,6 +63,10 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
|
||||
public bool ShouldBeVisible => !string.IsNullOrEmpty(Name);
|
||||
|
||||
public bool HasParameters => Command.HasParameters;
|
||||
|
||||
public ObservableCollection<ArgumentItemViewModel> Parameters => Command.Parameters;
|
||||
|
||||
public List<IContextItemViewModel> AllCommands
|
||||
{
|
||||
get
|
||||
@@ -181,14 +186,9 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
MoreCommands = more
|
||||
.Select(item =>
|
||||
{
|
||||
if (item is ICommandContextItem contextItem)
|
||||
{
|
||||
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SeparatorContextItemViewModel() as IContextItemViewModel;
|
||||
}
|
||||
return item is ICommandContextItem contextItem
|
||||
? new CommandContextItemViewModel(contextItem, PageContext)
|
||||
: new SeparatorContextItemViewModel() as IContextItemViewModel;
|
||||
})
|
||||
.ToList();
|
||||
}
|
||||
@@ -338,14 +338,9 @@ public partial class CommandItemViewModel : ExtensionObjectViewModel, ICommandBa
|
||||
var newContextMenu = more
|
||||
.Select(item =>
|
||||
{
|
||||
if (item is CommandContextItem contextItem)
|
||||
{
|
||||
return new CommandContextItemViewModel(contextItem, PageContext) as IContextItemViewModel;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new SeparatorContextItemViewModel() as IContextItemViewModel;
|
||||
}
|
||||
return item is CommandContextItem contextItem
|
||||
? new CommandContextItemViewModel(contextItem, PageContext)
|
||||
: new SeparatorContextItemViewModel() as IContextItemViewModel;
|
||||
})
|
||||
.ToList();
|
||||
lock (MoreCommands)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// 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 Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
@@ -29,6 +30,10 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
|
||||
public IconInfoViewModel Icon { get; private set; }
|
||||
|
||||
public bool HasParameters { get; set; }
|
||||
|
||||
public ObservableCollection<ArgumentItemViewModel> Parameters { get; private set; } = new();
|
||||
|
||||
public CommandViewModel(ICommand? command, WeakReference<IPageContext> pageContext)
|
||||
: base(pageContext)
|
||||
{
|
||||
@@ -51,6 +56,7 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
|
||||
Id = model.Id ?? string.Empty;
|
||||
Name = model.Name ?? string.Empty;
|
||||
|
||||
IsFastInitialized = true;
|
||||
}
|
||||
|
||||
@@ -80,6 +86,22 @@ public partial class CommandViewModel : ExtensionObjectViewModel
|
||||
UpdateProperty(nameof(Icon));
|
||||
}
|
||||
|
||||
// TODO! we can probably make this a slow initialization
|
||||
if (model is IInvokableCommandWithParameters withParams)
|
||||
{
|
||||
HasParameters = true;
|
||||
|
||||
if (withParams.Parameters is ICommandArgument[] parameters)
|
||||
{
|
||||
foreach (var p in parameters)
|
||||
{
|
||||
var paramViewModel = new ArgumentItemViewModel(new(p), PageContext);
|
||||
paramViewModel.InitializeProperties();
|
||||
Parameters.Add(paramViewModel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
model.PropChanged += Model_PropChanged;
|
||||
}
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
|
||||
{
|
||||
if (PrimaryCommand != null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(PrimaryCommand.Command.Model, PrimaryCommand.Model));
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(PrimaryCommand));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ public partial class ContentPageViewModel : PageViewModel, ICommandBarContext
|
||||
{
|
||||
if (SecondaryCommand != null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(SecondaryCommand.Command.Model, SecondaryCommand.Model));
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(SecondaryCommand));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -225,7 +225,7 @@ public partial class ContextMenuViewModel : ObservableObject,
|
||||
}
|
||||
else
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command.Command.Model, command.Model));
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(command));
|
||||
UpdateContextItems();
|
||||
return ContextKeybindingResult.Hide;
|
||||
}
|
||||
|
||||
@@ -132,7 +132,11 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
stopwatch.Start();
|
||||
|
||||
var newItems = _model.Unsafe!.GetItems();
|
||||
var getItemsTime = stopwatch.ElapsedMilliseconds;
|
||||
|
||||
// Collect all the items into new viewmodels
|
||||
Collection<ListItemViewModel> newViewModels = [];
|
||||
@@ -169,6 +173,13 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
|
||||
// TODO: Iterate over everything in Items, and prune items from the
|
||||
// cache if we don't need them anymore
|
||||
stopwatch.Stop();
|
||||
var initializeTime = stopwatch.ElapsedMilliseconds - getItemsTime;
|
||||
WeakReferenceMessenger.Default.Send<FetchItemsMetricsMessage>(new(
|
||||
newViewModels.Count,
|
||||
getItemsTime,
|
||||
initializeTime
|
||||
));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -297,13 +308,12 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.Command.Model, item.Model));
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item));
|
||||
}
|
||||
else if (ShowEmptyContent && EmptyContent.PrimaryCommand?.Model.Unsafe != null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(
|
||||
EmptyContent.PrimaryCommand.Command.Model,
|
||||
EmptyContent.PrimaryCommand.Model));
|
||||
EmptyContent.PrimaryCommand));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,14 +325,13 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
{
|
||||
if (item.SecondaryCommand != null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.SecondaryCommand.Command.Model, item.Model));
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(item.SecondaryCommand, item));
|
||||
}
|
||||
}
|
||||
else if (ShowEmptyContent && EmptyContent.SecondaryCommand?.Model.Unsafe != null)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(
|
||||
EmptyContent.SecondaryCommand.Command.Model,
|
||||
EmptyContent.SecondaryCommand.Model));
|
||||
EmptyContent.SecondaryCommand));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,6 +379,15 @@ public partial class ListViewModel : PageViewModel, IDisposable
|
||||
}
|
||||
|
||||
TextToSuggest = item.TextToSuggest;
|
||||
|
||||
if (item.HasParameters)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<UpdateParametersMessage>(new(item.Parameters));
|
||||
}
|
||||
else
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<UpdateParametersMessage>(new(null));
|
||||
}
|
||||
});
|
||||
|
||||
_lastSelectedItem = item;
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
|
||||
public record FetchItemsMetricsMessage(int ItemCount, long GetItemsTime, long InitializeItemsTime)
|
||||
{
|
||||
}
|
||||
@@ -18,12 +18,13 @@ public record PerformCommandMessage
|
||||
|
||||
public bool WithAnimation { get; set; } = true;
|
||||
|
||||
public PerformCommandMessage(ExtensionObject<ICommand> command)
|
||||
{
|
||||
Command = command;
|
||||
Context = null;
|
||||
}
|
||||
public ICommandArgument?[] Arguments { get; set; } = [];
|
||||
|
||||
// public PerformCommandMessage(ExtensionObject<ICommand> command)
|
||||
// {
|
||||
// Command = command;
|
||||
// Context = null;
|
||||
// }
|
||||
public PerformCommandMessage(ExtensionObject<ICommand> command, ExtensionObject<IListItem> context)
|
||||
{
|
||||
Command = command;
|
||||
@@ -48,6 +49,16 @@ public record PerformCommandMessage
|
||||
Context = contextCommand.Model.Unsafe;
|
||||
}
|
||||
|
||||
public PerformCommandMessage(CommandItemViewModel item, CommandItemViewModel? context = null)
|
||||
{
|
||||
Command = item.Command.Model;
|
||||
Context = context?.Model.Unsafe ?? item.Model.Unsafe;
|
||||
if (item.Parameters != null && item.Parameters.Any())
|
||||
{
|
||||
Arguments = item.Parameters.Select(p => p.Model.Unsafe).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public PerformCommandMessage(ConfirmResultViewModel vm)
|
||||
{
|
||||
Command = vm.PrimaryCommand.Model;
|
||||
|
||||
@@ -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.Core.ViewModels.Models;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
|
||||
public record RequestOpenPickerMessage(ExtensionObject<ICommandArgument> Argument)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
|
||||
public record UpdateParametersMessage(IEnumerable<ArgumentItemViewModel>? Parameters)
|
||||
{
|
||||
}
|
||||
@@ -93,7 +93,7 @@ public partial class ShellViewModel : ObservableObject,
|
||||
_rootPage = _rootPageService.GetRootPage();
|
||||
|
||||
// This sends a message to us to load the root page view model.
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(new ExtensionObject<ICommand>(_rootPage)));
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(new ExtensionObject<ICommand>(_rootPage), new ExtensionObject<ICommandContextItem>(null)));
|
||||
|
||||
// Now that the root page is loaded, do any post-load work that the root page service needs to do.
|
||||
// This runs asynchronously, on a background thread.
|
||||
@@ -217,6 +217,16 @@ public partial class ShellViewModel : ObservableObject,
|
||||
// Note: Originally we set our page back in the ViewModel here, but that now happens in response to the Frame navigating triggered from the above
|
||||
// See RootFrame_Navigated event handler.
|
||||
}
|
||||
else if (command is IInvokableCommandWithParameters commandWithParams)
|
||||
{
|
||||
Logger.LogDebug($"Invoking command with args");
|
||||
|
||||
// var args = ArgumentsViewModel.Arguments;
|
||||
// var aa = args.Select(a => a.Model.Unsafe).ToArray() ?? [];
|
||||
// message.Arguments = aa;
|
||||
WeakReferenceMessenger.Default.Send<BeginInvokeMessage>();
|
||||
HandleInvokeCommandWithArgs(message, commandWithParams, host);
|
||||
}
|
||||
else if (command is IInvokableCommand invokable)
|
||||
{
|
||||
Logger.LogDebug($"Invoking command");
|
||||
@@ -276,6 +286,44 @@ public partial class ShellViewModel : ObservableObject,
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleInvokeCommandWithArgs(PerformCommandMessage message, IInvokableCommandWithParameters invokable, AppExtensionHost? host)
|
||||
{
|
||||
// TODO GH #525 This needs more better locking.
|
||||
lock (_invokeLock)
|
||||
{
|
||||
if (_handleInvokeTask != null)
|
||||
{
|
||||
// do nothing - a command is already doing a thing
|
||||
}
|
||||
else
|
||||
{
|
||||
_handleInvokeTask = Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Call out to extension process.
|
||||
// * May fail!
|
||||
// * May never return!
|
||||
var result = invokable.InvokeWithArgs(message.Context, message.Arguments);
|
||||
|
||||
// But if it did succeed, we need to handle the result.
|
||||
UnsafeHandleCommandResult(result);
|
||||
|
||||
_handleInvokeTask = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_handleInvokeTask = null;
|
||||
|
||||
// TODO: It would be better to do this as a page exception, rather
|
||||
// than a silent log message.
|
||||
host?.Log(ex.Message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UnsafeHandleCommandResult(ICommandResult? result)
|
||||
{
|
||||
if (result == null)
|
||||
|
||||
@@ -23,6 +23,12 @@ public partial class BuiltInsCommandProvider : CommandProvider
|
||||
[
|
||||
new CommandItem(openSettings) { Subtitle = Properties.Resources.builtin_open_settings_subtitle },
|
||||
new CommandItem(_newExtension) { Title = _newExtension.Title, Subtitle = Properties.Resources.builtin_new_extension_subtitle },
|
||||
new ListItem(new CommandWithParams() { Name = "Invoke with params 2" })
|
||||
{
|
||||
Title = "Do a thing with a string",
|
||||
Subtitle = "This command requires more input",
|
||||
Icon = new IconInfo("\uE961"),
|
||||
}
|
||||
];
|
||||
|
||||
public override IFallbackCommandItem[] FallbackCommands() =>
|
||||
@@ -40,4 +46,46 @@ public partial class BuiltInsCommandProvider : CommandProvider
|
||||
}
|
||||
|
||||
public override void InitializeWithHost(IExtensionHost host) => BuiltinsExtensionHost.Instance.Initialize(host);
|
||||
|
||||
internal sealed partial class CommandWithParams : InvokableCommand, IInvokableCommandWithParameters
|
||||
{
|
||||
public ICommandArgument[] Parameters => [new TextParam("Test")];
|
||||
|
||||
public ICommandResult InvokeWithArgs(object sender, ICommandArgument[] args)
|
||||
{
|
||||
if (args.Length > 0)
|
||||
{
|
||||
var arg = args[0];
|
||||
var msg = $"Arg {arg.Name} = {arg.Value}";
|
||||
var toast = new ToastStatusMessage(new StatusMessage() { Message = msg, State = MessageState.Success });
|
||||
toast.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
var toast = new ToastStatusMessage(new StatusMessage() { Message = "didn't work homes", State = MessageState.Error });
|
||||
toast.Show();
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class TextParam(string name, bool required = true) : BaseObservable, ICommandArgument
|
||||
{
|
||||
public string Name => name;
|
||||
|
||||
public bool Required => required;
|
||||
|
||||
public ParameterType Type => ParameterType.Text;
|
||||
|
||||
public object? Value { get; set; }
|
||||
|
||||
public string DisplayName => string.Empty;
|
||||
|
||||
public IIconInfo? Icon => null;
|
||||
|
||||
public void ShowPicker(ulong hostHwnd)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// 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.
|
||||
|
||||
namespace Microsoft.CmdPal.UI.ViewModels;
|
||||
|
||||
// public partial class ParameterViewModel : ExtensionObjectViewModel
|
||||
// {
|
||||
// public ExtensionObject<ICommandParameter> Model { get; private set; } = new(null);
|
||||
|
||||
// protected bool IsInitialized { get; private set; }
|
||||
|
||||
// // values from ICommandParameter
|
||||
// public string Name { get; private set; } = string.Empty;
|
||||
|
||||
// public ParameterType Type { get; private set; } = ParameterType.Text;
|
||||
|
||||
// public bool Required { get; private set; } = true;
|
||||
|
||||
// public ParameterViewModel(ICommandParameter? parameter, WeakReference<IPageContext> pageContext)
|
||||
// : base(pageContext)
|
||||
// {
|
||||
// Model = new(parameter);
|
||||
// }
|
||||
|
||||
// public override void InitializeProperties()
|
||||
// {
|
||||
// if (IsInitialized)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var model = Model.Unsafe;
|
||||
// if (model == null)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// Name = model.Name ?? string.Empty;
|
||||
// Type = model.Type;
|
||||
// Required = model.Required;
|
||||
// }
|
||||
// }
|
||||
@@ -6,6 +6,7 @@ using ManagedCommon;
|
||||
using Microsoft.CmdPal.Common.Helpers;
|
||||
using Microsoft.CmdPal.Common.Services;
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CmdPal.Ext.Actions;
|
||||
using Microsoft.CmdPal.Ext.Apps;
|
||||
using Microsoft.CmdPal.Ext.Bookmarks;
|
||||
using Microsoft.CmdPal.Ext.Calc;
|
||||
@@ -133,6 +134,7 @@ public partial class App : Application
|
||||
services.AddSingleton<ICommandProvider, BuiltInsCommandProvider>();
|
||||
services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>();
|
||||
services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>();
|
||||
services.AddSingleton<ICommandProvider, AgentsTestCommandsProvider>();
|
||||
|
||||
// Models
|
||||
services.AddSingleton<TopLevelCommandManager>();
|
||||
|
||||
@@ -16,17 +16,25 @@
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
<!-- Search box -->
|
||||
<TextBox
|
||||
x:Name="FilterBox"
|
||||
MinHeight="32"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
KeyDown="FilterBox_KeyDown"
|
||||
PlaceholderText="{x:Bind CurrentPageViewModel.PlaceholderText, Converter={StaticResource PlaceholderTextConverter}, Mode=OneWay}"
|
||||
PreviewKeyDown="FilterBox_PreviewKeyDown"
|
||||
PreviewKeyUp="FilterBox_PreviewKeyUp"
|
||||
Style="{StaticResource SearchTextBoxStyle}"
|
||||
TextChanged="FilterBox_TextChanged" />
|
||||
<!-- Disabled Description="{x:Bind CurrentPageViewModel.TextToSuggest, Mode=OneWay}" for now, needs more work -->
|
||||
<StackPanel Orientation="Horizontal" x:Name="Root">
|
||||
|
||||
<!-- Search box -->
|
||||
<TextBox
|
||||
x:Name="FilterBox"
|
||||
MinHeight="32"
|
||||
VerticalAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
KeyDown="FilterBox_KeyDown"
|
||||
PlaceholderText="{x:Bind CurrentPageViewModel.PlaceholderText, Converter={StaticResource PlaceholderTextConverter}, Mode=OneWay}"
|
||||
PreviewKeyDown="FilterBox_PreviewKeyDown"
|
||||
PreviewKeyUp="FilterBox_PreviewKeyUp"
|
||||
Style="{StaticResource SearchTextBoxStyle}"
|
||||
TextChanged="FilterBox_TextChanged" />
|
||||
<!-- Disabled Description="{x:Bind CurrentPageViewModel.TextToSuggest, Mode=OneWay}" for now, needs more work -->
|
||||
|
||||
<StackPanel Orientation="Horizontal" x:Name="ParametersPanel">
|
||||
<!-- Parameter text boxes will be added dynamically in code-behind and bound to ArgumentsViewModel -->
|
||||
</StackPanel>
|
||||
<!-- Add DataContext for ArgumentsViewModel if needed for future XAML binding -->
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
|
||||
@@ -21,6 +21,7 @@ namespace Microsoft.CmdPal.UI.Controls;
|
||||
public sealed partial class SearchBar : UserControl,
|
||||
IRecipient<GoHomeMessage>,
|
||||
IRecipient<FocusSearchBoxMessage>,
|
||||
IRecipient<UpdateParametersMessage>,
|
||||
ICurrentPageAware
|
||||
{
|
||||
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
|
||||
@@ -64,11 +65,17 @@ public sealed partial class SearchBar : UserControl,
|
||||
}
|
||||
}
|
||||
|
||||
public ArgumentsViewModel ArgumentsViewModel { get; private set; } = new ArgumentsViewModel();
|
||||
|
||||
public static readonly DependencyProperty ArgumentsViewModelProperty =
|
||||
DependencyProperty.Register(nameof(ArgumentsViewModel), typeof(ArgumentsViewModel), typeof(SearchBar), new PropertyMetadata(null));
|
||||
|
||||
public SearchBar()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
WeakReferenceMessenger.Default.Register<GoHomeMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<FocusSearchBoxMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<UpdateParametersMessage>(this);
|
||||
}
|
||||
|
||||
public void ClearSearch()
|
||||
@@ -290,4 +297,79 @@ public sealed partial class SearchBar : UserControl,
|
||||
public void Receive(GoHomeMessage message) => ClearSearch();
|
||||
|
||||
public void Receive(FocusSearchBoxMessage message) => FilterBox.Focus(Microsoft.UI.Xaml.FocusState.Programmatic);
|
||||
|
||||
public void Receive(UpdateParametersMessage message)
|
||||
{
|
||||
ParametersPanel.Children.Clear();
|
||||
ArgumentsViewModel.Arguments.Clear();
|
||||
if (message.Parameters != null)
|
||||
{
|
||||
foreach (var param in message.Parameters)
|
||||
{
|
||||
// var argVm = new ArgumentItemViewModel { Name = param.Name };
|
||||
ArgumentsViewModel.Arguments.Add(param);
|
||||
|
||||
if (param.Type == CommandPalette.Extensions.ParameterType.Text)
|
||||
{
|
||||
var textBox = new TextBox
|
||||
{
|
||||
MinHeight = 24,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
VerticalContentAlignment = VerticalAlignment.Stretch,
|
||||
PlaceholderText = param.Name,
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
};
|
||||
textBox.SetBinding(TextBox.TextProperty, new Microsoft.UI.Xaml.Data.Binding
|
||||
{
|
||||
Source = param,
|
||||
Path = new PropertyPath("Value"),
|
||||
Mode = Microsoft.UI.Xaml.Data.BindingMode.TwoWay,
|
||||
UpdateSourceTrigger = Microsoft.UI.Xaml.Data.UpdateSourceTrigger.PropertyChanged,
|
||||
});
|
||||
ParametersPanel.Children.Add(textBox);
|
||||
}
|
||||
|
||||
// else if (param.Type == ParameterType.Enum)
|
||||
// {
|
||||
// var comboBox = new ComboBox
|
||||
// {
|
||||
// MinHeight = 24,
|
||||
// VerticalAlignment = VerticalAlignment.Center,
|
||||
// VerticalContentAlignment = VerticalAlignment.Stretch,
|
||||
// PlaceholderText = param.Name,
|
||||
// Margin = new Thickness(4, 0, 4, 0),
|
||||
// };
|
||||
// comboBox.SetBinding(ComboBox.SelectedItemProperty, new Microsoft.UI.Xaml.Data.Binding
|
||||
// {
|
||||
// Source = param,
|
||||
// Path = new PropertyPath("Value"),
|
||||
// Mode = Microsoft.UI.Xaml.Data.BindingMode.TwoWay,
|
||||
// UpdateSourceTrigger = Microsoft.UI.Xaml.Data.UpdateSourceTrigger.PropertyChanged,
|
||||
// });
|
||||
// ParametersPanel.Children.Add(comboBox);
|
||||
// }
|
||||
else if (param.Type == CommandPalette.Extensions.ParameterType.Custom)
|
||||
{
|
||||
// Add a button, and when they click it, trigger the custom action
|
||||
var button = new Button
|
||||
{
|
||||
Content = param.Name,
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
};
|
||||
button.Click += (s, e) =>
|
||||
{
|
||||
param.OpenPicker();
|
||||
};
|
||||
button.SetBinding(Button.ContentProperty, new Microsoft.UI.Xaml.Data.Binding
|
||||
{
|
||||
Source = param,
|
||||
Path = new PropertyPath("DisplayName"),
|
||||
Mode = Microsoft.UI.Xaml.Data.BindingMode.OneWay,
|
||||
UpdateSourceTrigger = Microsoft.UI.Xaml.Data.UpdateSourceTrigger.PropertyChanged,
|
||||
});
|
||||
ParametersPanel.Children.Add(button);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Tracing;
|
||||
using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.PowerToys.Telemetry.Events;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Events;
|
||||
|
||||
[EventData]
|
||||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]
|
||||
public class FetchItemsMetrics : EventBase, IEvent
|
||||
{
|
||||
public int ItemCount { get; init; }
|
||||
|
||||
public long GetItemsTime { get; init; }
|
||||
|
||||
public long InitializeItemsTime { get; init; }
|
||||
|
||||
public FetchItemsMetrics(int itemCount, long getItemsTime, long initializeItemsTime)
|
||||
{
|
||||
ItemCount = itemCount;
|
||||
GetItemsTime = getItemsTime;
|
||||
InitializeItemsTime = initializeItemsTime;
|
||||
}
|
||||
|
||||
public new string EventName => "CmdPalFetchItemsMetrics";
|
||||
|
||||
public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage;
|
||||
}
|
||||
@@ -20,12 +20,14 @@ namespace Microsoft.CmdPal.UI;
|
||||
/// </summary>
|
||||
internal sealed class TelemetryForwarder :
|
||||
IRecipient<BeginInvokeMessage>,
|
||||
IRecipient<CmdPalInvokeResultMessage>
|
||||
IRecipient<CmdPalInvokeResultMessage>,
|
||||
IRecipient<FetchItemsMetricsMessage>
|
||||
{
|
||||
public TelemetryForwarder()
|
||||
{
|
||||
WeakReferenceMessenger.Default.Register<BeginInvokeMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<CmdPalInvokeResultMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<FetchItemsMetricsMessage>(this);
|
||||
}
|
||||
|
||||
public void Receive(CmdPalInvokeResultMessage message)
|
||||
@@ -37,4 +39,9 @@ internal sealed class TelemetryForwarder :
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new BeginInvoke());
|
||||
}
|
||||
|
||||
public void Receive(FetchItemsMetricsMessage message)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new FetchItemsMetrics(message.ItemCount, message.GetItemsTime, message.InitializeItemsTime));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
|
||||
<!-- TODO!: I'm pretty sure this is just because my machine is on an old VS / .net SDK -->
|
||||
<WarningsNotAsErrors>$(WarningsNotAsErrors);IL2059;</WarningsNotAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(EnableCmdPalAOT)' == 'true'">
|
||||
@@ -135,6 +138,7 @@
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.WindowsTerminal\Microsoft.CmdPal.Ext.WindowsTerminal.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.WindowWalker\Microsoft.CmdPal.Ext.WindowWalker.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.WinGet\Microsoft.CmdPal.Ext.WinGet.csproj" />
|
||||
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Actions\Microsoft.CmdPal.Ext.Actions.csproj" />
|
||||
|
||||
<ProjectReference Include="..\Microsoft.Terminal.UI\Microsoft.Terminal.UI.vcxproj">
|
||||
<ReferenceOutputAssembly>True</ReferenceOutputAssembly>
|
||||
|
||||
@@ -85,5 +85,6 @@
|
||||
<Capabilities>
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
<rescap:Capability Name="unvirtualizedResources" />
|
||||
<rescap:Capability Name="contacts" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
|
||||
@@ -8,6 +8,7 @@ using CommunityToolkit.WinUI;
|
||||
using ManagedCommon;
|
||||
using Microsoft.CmdPal.Core.ViewModels;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Messages;
|
||||
using Microsoft.CmdPal.Core.ViewModels.Models;
|
||||
using Microsoft.CmdPal.UI.Events;
|
||||
using Microsoft.CmdPal.UI.Settings;
|
||||
using Microsoft.CmdPal.UI.ViewModels;
|
||||
@@ -17,7 +18,6 @@ using Microsoft.PowerToys.Telemetry;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
|
||||
@@ -42,6 +42,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
IRecipient<ShowConfirmationMessage>,
|
||||
IRecipient<ShowToastMessage>,
|
||||
IRecipient<NavigateToPageMessage>,
|
||||
IRecipient<RequestOpenPickerMessage>,
|
||||
INotifyPropertyChanged
|
||||
{
|
||||
private readonly DispatcherQueue _queue = DispatcherQueue.GetForCurrentThread();
|
||||
@@ -82,6 +83,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
WeakReferenceMessenger.Default.Register<ShowConfirmationMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<ShowToastMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<NavigateToPageMessage>(this);
|
||||
WeakReferenceMessenger.Default.Register<RequestOpenPickerMessage>(this);
|
||||
|
||||
AddHandler(PreviewKeyDownEvent, new KeyEventHandler(ShellPage_OnPreviewKeyDown), true);
|
||||
AddHandler(PointerPressedEvent, new PointerEventHandler(ShellPage_OnPointerPressed), true);
|
||||
@@ -286,6 +288,19 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
|
||||
public void Receive(ClearSearchMessage message) => SearchBox.ClearSearch();
|
||||
|
||||
public void Receive(RequestOpenPickerMessage message)
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
var argument = message.Argument.Unsafe;
|
||||
if (argument != null)
|
||||
{
|
||||
var hwnd = (ulong)WinRT.Interop.WindowNative.GetWindowHandle(App.Current.AppWindow).ToInt64();
|
||||
argument.ShowPicker(hwnd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Receive(HotkeySummonMessage message)
|
||||
{
|
||||
_ = DispatcherQueue.TryEnqueue(() => SummonOnUiThread(message));
|
||||
@@ -447,7 +462,7 @@ public sealed partial class ShellPage : Microsoft.UI.Xaml.Controls.Page,
|
||||
{
|
||||
if (sender is Button button && button.DataContext is CommandViewModel commandViewModel)
|
||||
{
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(commandViewModel.Model));
|
||||
WeakReferenceMessenger.Default.Send<PerformCommandMessage>(new(commandViewModel.Model, new ExtensionObject<IListItem>(null)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1410,8 +1410,8 @@ interface IDetailsLink requires IDetailsData {
|
||||
Windows.Foundation.Uri Link { get; };
|
||||
String Text { get; };
|
||||
}
|
||||
interface IDetailsCommand requires IDetailsData {
|
||||
ICommand Command { get; };
|
||||
interface IDetailsCommands requires IDetailsData {
|
||||
ICommand[] Commands { get; };
|
||||
}
|
||||
[uuid("58070392-02bb-4e89-9beb-47ceb8c3d741")]
|
||||
interface IDetailsSeparator requires IDetailsData {}
|
||||
@@ -2112,13 +2112,84 @@ for highly complex action chaining.
|
||||
I had originally started to spec this out as:
|
||||
|
||||
```cs
|
||||
enum ParameterType
|
||||
{
|
||||
Text,
|
||||
File,
|
||||
Files,
|
||||
Enum,
|
||||
Entity
|
||||
};
|
||||
|
||||
interface ICommandParameter
|
||||
{
|
||||
ParameterType Type { get; };
|
||||
String Name { get; };
|
||||
Boolean Required{ get; };
|
||||
// TODO! values for enums?
|
||||
// TODO! dynamic values for enums? like GetValues(string query)
|
||||
// TODO! files might want to restrict types? but now we're a file picker and need that whole API
|
||||
// TODO! parameters with more than one value? Like,
|
||||
// SendMessage(People[] to, String message)
|
||||
};
|
||||
|
||||
interface ICommandArgument
|
||||
{
|
||||
String Name { get; };
|
||||
Object Value { get; };
|
||||
};
|
||||
|
||||
interface IInvokableCommandWithParameters requires ICommand {
|
||||
ActionParameters Parameters { get; };
|
||||
CommandResult InvokeWithArgs(ActionArguments args);
|
||||
}
|
||||
ICommandParameter[] Parameters { get; };
|
||||
ICommandResult InvokeWithArgs(Object sender, ICommandArgument[] args);
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
And `ActionParameters` would be a set of `{ type, name, required}` structs,
|
||||
TODO! Mike:
|
||||
We should add like, a `CustomPicker` parameter type, which would allow
|
||||
extensions to define their own custom pickers for parameters. Then when we go to fill the argument, we'd call something like `ShowPickerAsync(ICommandParameter param)` and let them fill in the value. We don't care what the value is.
|
||||
|
||||
So it'd be more like
|
||||
|
||||
```c#
|
||||
enum ParameterType
|
||||
{
|
||||
Text,
|
||||
// File,
|
||||
// Files,
|
||||
Enum,
|
||||
Custom
|
||||
};
|
||||
|
||||
// interface IArgumentEnumValue requires INotifyPropChanged
|
||||
// {
|
||||
// String Name { get; };
|
||||
// IIconInfo Icon { get; };
|
||||
// }
|
||||
interface ICommandArgument requires INotifyPropChanged
|
||||
{
|
||||
ParameterType Type { get; };
|
||||
String Name { get; };
|
||||
Boolean Required{ get; };
|
||||
|
||||
Object Value { get; set; };
|
||||
String DisplayName { get; };
|
||||
IIconInfo Icon { get; };
|
||||
|
||||
void ShowPicker(UInt64 hostHwnd);
|
||||
// todo
|
||||
// IArgumentEnumValue[] GetValues();
|
||||
};
|
||||
|
||||
interface IInvokableCommandWithParameters requires ICommand {
|
||||
ICommandArgument[] Parameters { get; };
|
||||
ICommandResult InvokeWithArgs(Object sender, ICommandArgument[] args);
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
And `CommandParameters` would be a set of `{ type, name, required }` structs,
|
||||
which would specify the parameters that the action needs. Simple types would be
|
||||
`string`, `file`, `file[]`, `enum` (with possible values), etc.
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// 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;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using ManagedCsWin32;
|
||||
using WinRT;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Actions;
|
||||
|
||||
internal static class ActionRuntimeFactory
|
||||
{
|
||||
private const string ActionRuntimeClsidStr = "C36FEF7E-35F3-4192-9F2C-AF1FD425FB85";
|
||||
|
||||
// typeof(Windows.AI.Actions.IActionRuntime).GUID
|
||||
private static readonly Guid IActionRuntimeIID = Guid.Parse("206EFA2C-C909-508A-B4B0-9482BE96DB9C");
|
||||
|
||||
public static unsafe global::Windows.AI.Actions.ActionRuntime CreateActionRuntime()
|
||||
{
|
||||
IntPtr abiPtr = default;
|
||||
try
|
||||
{
|
||||
var classId = Guid.Parse(ActionRuntimeClsidStr);
|
||||
var iid = IActionRuntimeIID;
|
||||
|
||||
var hresult = Ole32.CoCreateInstance(ref Unsafe.AsRef(in classId), IntPtr.Zero, CLSCTX.LocalServer, ref iid, out abiPtr);
|
||||
Marshal.ThrowExceptionForHR(hresult);
|
||||
|
||||
return MarshalInterface<global::Windows.AI.Actions.ActionRuntime>.FromAbi(abiPtr);
|
||||
}
|
||||
finally
|
||||
{
|
||||
MarshalInspectable<object>.DisposeAbi(abiPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.AI.Actions;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Actions;
|
||||
|
||||
public static class ActionRuntimeManager
|
||||
{
|
||||
private static readonly Lazy<Task<ActionRuntime?>> _lazyRuntime = new(InitializeAsync);
|
||||
|
||||
public static Task<ActionRuntime?> InstanceAsync => _lazyRuntime.Value;
|
||||
|
||||
private static async Task<ActionRuntime?> InitializeAsync()
|
||||
{
|
||||
// If we tried 3 times and failed, should we think the action runtime is not working?
|
||||
// then we should not use it anymore.
|
||||
const int maxAttempts = 3;
|
||||
|
||||
for (var attempt = 1; attempt <= maxAttempts; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
var runtime = ActionRuntimeFactory.CreateActionRuntime();
|
||||
await Task.Delay(500);
|
||||
|
||||
return runtime;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Logger.LogError($"Attempt {attempt} to initialize ActionRuntime failed: {ex.Message}");
|
||||
|
||||
// if (attempt == maxAttempts)
|
||||
// {
|
||||
// Logger.LogError($"Failed to initialize ActionRuntime: {ex.Message}");
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Actions;
|
||||
|
||||
[Guid("e87f18cd-985d-4bfc-aacc-117ef57a675d")]
|
||||
public sealed partial class AgentsTest : IExtension, IDisposable
|
||||
{
|
||||
private readonly ManualResetEvent _extensionDisposedEvent;
|
||||
|
||||
private readonly AgentsTestCommandsProvider _provider = new();
|
||||
|
||||
public AgentsTest(ManualResetEvent extensionDisposedEvent)
|
||||
{
|
||||
this._extensionDisposedEvent = extensionDisposedEvent;
|
||||
}
|
||||
|
||||
public object? GetProvider(ProviderType providerType)
|
||||
{
|
||||
return providerType switch
|
||||
{
|
||||
ProviderType.Commands => _provider,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
public void Dispose() => this._extensionDisposedEvent.Set();
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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.Generic;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.AI.Actions;
|
||||
using Windows.Foundation.Metadata;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Actions;
|
||||
|
||||
public partial class AgentsTestCommandsProvider : CommandProvider
|
||||
{
|
||||
private readonly List<ICommandItem> _commands;
|
||||
private readonly ScriptsTestPage _scriptsPage;
|
||||
private static ActionRuntime? _actionRuntime;
|
||||
private bool _init;
|
||||
|
||||
public AgentsTestCommandsProvider()
|
||||
{
|
||||
DisplayName = "Agents for Windows";
|
||||
Icon = IconHelpers.FromRelativePath("Assets\\StoreLogo.png");
|
||||
Id = "Actions";
|
||||
_scriptsPage = new ScriptsTestPage();
|
||||
_commands = [
|
||||
new CommandItem(new AgentsTestPage())
|
||||
{
|
||||
Title = DisplayName,
|
||||
Subtitle = "Use @ to invoke various agents",
|
||||
},
|
||||
new CommandItem(_scriptsPage)
|
||||
{
|
||||
Title = "Script commands",
|
||||
Subtitle = "What if we were just raycast",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public override ICommandItem[] TopLevelCommands()
|
||||
{
|
||||
if (!_init)
|
||||
{
|
||||
_init = true;
|
||||
if (ApiInformation.IsApiContractPresent("Windows.AI.Actions.ActionsContract", 4))
|
||||
{
|
||||
_actionRuntime = ActionRuntimeManager.InstanceAsync.GetAwaiter().GetResult();
|
||||
if (_actionRuntime != null)
|
||||
{
|
||||
_commands.Add(new CommandItem(new ActionsTestPage(_actionRuntime))
|
||||
{
|
||||
Title = "Actions",
|
||||
Subtitle = "Windows Actions Framework",
|
||||
Icon = Icons.ActionsPng,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<ICommandItem> commands = [.. _commands];
|
||||
//// Add the contacts list page
|
||||
// commands.Add(new CommandItem(new ContactsListPage())
|
||||
// {
|
||||
// Title = "Contacts",
|
||||
// Subtitle = "Browse your contacts",
|
||||
// Icon = Icons.ContactInput,
|
||||
// });
|
||||
var topLevelScripts = _scriptsPage.GetItems();
|
||||
commands.InsertRange(0, topLevelScripts);
|
||||
|
||||
return commands.ToArray();
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="none" d="M4.24 4.24h119.53v119.53H4.24z"/><path fill="#293138" d="M109.01 28.64L71.28 6.24c-2.25-1.33-4.77-2-7.28-2s-5.03.67-7.28 2.01l-37.74 22.4c-4.5 2.67-7.28 7.61-7.28 12.96v44.8c0 5.35 2.77 10.29 7.28 12.96l37.73 22.4c2.25 1.34 4.76 2 7.28 2 2.51 0 5.03-.67 7.28-2l37.74-22.4c4.5-2.67 7.28-7.62 7.28-12.96V41.6c0-5.34-2.77-10.29-7.28-12.96zM79.79 98.59l.06 3.22c0 .39-.25.83-.55.99l-1.91 1.1c-.3.15-.56-.03-.56-.42l-.03-3.17c-1.63.68-3.29.84-4.34.42-.2-.08-.29-.37-.21-.71l.69-2.91c.06-.23.18-.46.34-.6.06-.06.12-.1.18-.13.11-.06.22-.07.31-.03 1.14.38 2.59.2 3.99-.5 1.78-.9 2.97-2.72 2.95-4.52-.02-1.64-.9-2.31-3.05-2.33-2.74.01-5.3-.53-5.34-4.57-.03-3.32 1.69-6.78 4.43-8.96l-.03-3.25c0-.4.24-.84.55-1l1.85-1.18c.3-.15.56.04.56.43l.03 3.25c1.36-.54 2.54-.69 3.61-.44.23.06.34.38.24.75l-.72 2.88c-.06.22-.18.44-.33.58a.77.77 0 01-.19.14c-.1.05-.19.06-.28.05-.49-.11-1.65-.36-3.48.56-1.92.97-2.59 2.64-2.58 3.88.02 1.48.77 1.93 3.39 1.97 3.49.06 4.99 1.58 5.03 5.09.05 3.44-1.79 7.15-4.61 9.41zm26.34-60.5l-35.7 22.05c-4.45 2.6-7.73 5.52-7.74 10.89v43.99c0 3.21 1.3 5.29 3.29 5.9-.65.11-1.32.19-1.98.19-2.09 0-4.15-.57-5.96-1.64l-37.73-22.4c-3.69-2.19-5.98-6.28-5.98-10.67V41.6c0-4.39 2.29-8.48 5.98-10.67l37.74-22.4c1.81-1.07 3.87-1.64 5.96-1.64s4.15.57 5.96 1.64l37.74 22.4c3.11 1.85 5.21 5.04 5.8 8.63-1.27-2.67-4.09-3.39-7.38-1.47z"/><path fill="#4FA847" d="M99.12 90.73l-9.4 5.62c-.25.15-.43.31-.43.61v2.46c0 .3.2.43.45.28l9.54-5.8c.25-.15.29-.42.29-.72v-2.17c0-.3-.2-.42-.45-.28z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 174 KiB |
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48" fill="none">
|
||||
<path d="M34.1423 7.32501C33.5634 5.35387 31.7547 4 29.7003 4L28.3488 4C26.1142 4 24.1985 5.59611 23.7952 7.79398L21.4805 20.4072L22.0549 18.4419C22.6319 16.4679 24.4419 15.1111 26.4986 15.1111H34.3524L37.6462 16.3942L40.8213 15.1111H39.8946C37.8401 15.1111 36.0315 13.7572 35.4525 11.7861L34.1423 7.32501Z" fill="url(#paint0_radial_56201_15503)"></path>
|
||||
<path d="M14.3307 40.656C14.9032 42.6366 16.7165 44 18.7783 44H21.6486C24.1592 44 26.2122 41.999 26.2767 39.4893L26.5893 27.3271L25.9354 29.5602C25.3577 31.5332 23.5481 32.8889 21.4923 32.8889L13.5732 32.8889L10.7499 31.3573L7.69336 32.8889H8.60461C10.6663 32.8889 12.4796 34.2522 13.0521 36.2329L14.3307 40.656Z" fill="url(#paint1_radial_56201_15503)"></path>
|
||||
<path d="M29.4993 4H13.46C8.87732 4 6.12772 10.0566 4.29466 16.1132C2.12296 23.2886 -0.718769 32.8852 7.50252 32.8852H14.4282C16.4978 32.8852 18.3147 31.5168 18.8835 29.5269C20.0876 25.3143 22.1978 17.9655 23.8554 12.3712C24.6977 9.52831 25.3993 7.08673 26.4762 5.56628C27.0799 4.71385 28.086 4 29.4993 4Z" fill="url(#paint2_linear_56201_15503)"></path>
|
||||
<path d="M29.4993 4H13.46C8.87732 4 6.12772 10.0566 4.29466 16.1132C2.12296 23.2886 -0.718769 32.8852 7.50252 32.8852H14.4282C16.4978 32.8852 18.3147 31.5168 18.8835 29.5269C20.0876 25.3143 22.1978 17.9655 23.8554 12.3712C24.6977 9.52831 25.3993 7.08673 26.4762 5.56628C27.0799 4.71385 28.086 4 29.4993 4Z" fill="url(#paint3_linear_56201_15503)"></path>
|
||||
<path d="M18.498 44H34.5374C39.12 44 41.8696 37.9424 43.7027 31.8848C45.8744 24.7081 48.7161 15.1098 40.4948 15.1098H33.5693C31.4996 15.1098 29.6827 16.4784 29.114 18.4684C27.9098 22.6817 25.7996 30.032 24.142 35.6273C23.2996 38.4708 22.598 40.9127 21.5212 42.4335C20.9175 43.286 19.9113 44 18.498 44Z" fill="url(#paint4_radial_56201_15503)"></path>
|
||||
<path d="M18.498 44H34.5374C39.12 44 41.8696 37.9424 43.7027 31.8848C45.8744 24.7081 48.7161 15.1098 40.4948 15.1098H33.5693C31.4996 15.1098 29.6827 16.4784 29.114 18.4684C27.9098 22.6817 25.7996 30.032 24.142 35.6273C23.2996 38.4708 22.598 40.9127 21.5212 42.4335C20.9175 43.286 19.9113 44 18.498 44Z" fill="url(#paint5_linear_56201_15503)"></path>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_56201_15503" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(38.005 20.5144) rotate(-129.304) scale(17.3033 16.2706)">
|
||||
<stop offset="0.0955758" stop-color="#00AEFF"></stop>
|
||||
<stop offset="0.773185" stop-color="#2253CE"></stop>
|
||||
<stop offset="1" stop-color="#0736C4"></stop>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint1_radial_56201_15503" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.1215 32.8171) rotate(51.84) scale(15.9912 15.5119)">
|
||||
<stop offset="0" stop-color="#FFB657"></stop>
|
||||
<stop offset="0.633728" stop-color="#FF5F3D"></stop>
|
||||
<stop offset="0.923392" stop-color="#C02B3C"></stop>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint2_linear_56201_15503" x1="12.5" y1="7.5" x2="14.7884" y2="33.9751" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.156162" stop-color="#0D91E1"></stop>
|
||||
<stop offset="0.487484" stop-color="#52B471"></stop>
|
||||
<stop offset="0.652394" stop-color="#98BD42"></stop>
|
||||
<stop offset="0.937361" stop-color="#FFC800"></stop>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_56201_15503" x1="14.5" y1="4" x2="15.7496" y2="32.8852" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#3DCBFF"></stop>
|
||||
<stop offset="0.246674" stop-color="#0588F7" stop-opacity="0"></stop>
|
||||
</linearGradient>
|
||||
<radialGradient id="paint4_radial_56201_15503" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(41.3187 12.2813) rotate(109.274) scale(38.3873 45.9867)">
|
||||
<stop offset="0.0661714" stop-color="#8C48FF"></stop>
|
||||
<stop offset="0.5" stop-color="#F2598A"></stop>
|
||||
<stop offset="0.895833" stop-color="#FFB152"></stop>
|
||||
</radialGradient>
|
||||
<linearGradient id="paint5_linear_56201_15503" x1="42.5859" y1="13.346" x2="42.5695" y2="21.2147" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.0581535" stop-color="#F8ADFA"></stop>
|
||||
<stop offset="0.708063" stop-color="#A86EDD" stop-opacity="0"></stop>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 128 128"><linearGradient id="a" x1="96.306" x2="25.454" y1="35.144" y2="98.431" gradientTransform="matrix(1 0 0 -1 0 128)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a9c8ff"/><stop offset="1" stop-color="#c7e6ff"/></linearGradient><path fill="url(#a)" fill-rule="evenodd" d="M7.2 110.5c-1.7 0-3.1-.7-4.1-1.9-1-1.2-1.3-2.9-.9-4.6l18.6-80.5c.8-3.4 4-6 7.4-6h92.6c1.7 0 3.1.7 4.1 1.9 1 1.2 1.3 2.9.9 4.6l-18.6 80.5c-.8 3.4-4 6-7.4 6H7.2z" clip-rule="evenodd" opacity=".8"/><linearGradient id="b" x1="25.336" x2="94.569" y1="98.33" y2="36.847" gradientTransform="matrix(1 0 0 -1 0 128)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2d4664"/><stop offset=".169" stop-color="#29405b"/><stop offset=".445" stop-color="#1e2f43"/><stop offset=".79" stop-color="#0c131b"/><stop offset="1"/></linearGradient><path fill="url(#b)" fill-rule="evenodd" d="M120.3 18.5H28.5c-2.9 0-5.7 2.3-6.4 5.2L3.7 104.3c-.7 2.9 1.1 5.2 4 5.2h91.8c2.9 0 5.7-2.3 6.4-5.2l18.4-80.5c.7-2.9-1.1-5.3-4-5.3z" clip-rule="evenodd"/><path fill="#2C5591" fill-rule="evenodd" d="M64.2 88.3h22.3c2.6 0 4.7 2.2 4.7 4.9s-2.1 4.9-4.7 4.9H64.2c-2.6 0-4.7-2.2-4.7-4.9s2.1-4.9 4.7-4.9zM78.7 66.5c-.4.8-1.2 1.6-2.6 2.6L34.6 98.9c-2.3 1.6-5.5 1-7.3-1.4-1.7-2.4-1.3-5.7.9-7.3l37.4-27.1v-.6l-23.5-25c-1.9-2-1.7-5.3.4-7.4 2.2-2 5.5-2 7.4 0l28.2 30c1.7 1.9 1.8 4.5.6 6.4z" clip-rule="evenodd"/><path fill="#FFF" fill-rule="evenodd" d="M77.6 65.5c-.4.8-1.2 1.6-2.6 2.6L33.6 97.9c-2.3 1.6-5.5 1-7.3-1.4-1.7-2.4-1.3-5.7.9-7.3l37.4-27.1v-.6l-23.5-25c-1.9-2-1.7-5.3.4-7.4 2.2-2 5.5-2 7.4 0l28.2 30c1.7 1.8 1.8 4.4.5 6.4zM63.5 87.8h22.3c2.6 0 4.7 2.1 4.7 4.6 0 2.6-2.1 4.6-4.7 4.6H63.5c-2.6 0-4.7-2.1-4.7-4.6 0-2.6 2.1-4.6 4.7-4.6z" clip-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><linearGradient id="python-original-a" gradientUnits="userSpaceOnUse" x1="70.252" y1="1237.476" x2="170.659" y2="1151.089" gradientTransform="matrix(.563 0 0 -.568 -29.215 707.817)"><stop offset="0" stop-color="#5A9FD4"/><stop offset="1" stop-color="#306998"/></linearGradient><linearGradient id="python-original-b" gradientUnits="userSpaceOnUse" x1="209.474" y1="1098.811" x2="173.62" y2="1149.537" gradientTransform="matrix(.563 0 0 -.568 -29.215 707.817)"><stop offset="0" stop-color="#FFD43B"/><stop offset="1" stop-color="#FFE873"/></linearGradient><path fill="url(#python-original-a)" d="M63.391 1.988c-4.222.02-8.252.379-11.8 1.007-10.45 1.846-12.346 5.71-12.346 12.837v9.411h24.693v3.137H29.977c-7.176 0-13.46 4.313-15.426 12.521-2.268 9.405-2.368 15.275 0 25.096 1.755 7.311 5.947 12.519 13.124 12.519h8.491V67.234c0-8.151 7.051-15.34 15.426-15.34h24.665c6.866 0 12.346-5.654 12.346-12.548V15.833c0-6.693-5.646-11.72-12.346-12.837-4.244-.706-8.645-1.027-12.866-1.008zM50.037 9.557c2.55 0 4.634 2.117 4.634 4.721 0 2.593-2.083 4.69-4.634 4.69-2.56 0-4.633-2.097-4.633-4.69-.001-2.604 2.073-4.721 4.633-4.721z" transform="translate(0 10.26)"/><path fill="url(#python-original-b)" d="M91.682 28.38v10.966c0 8.5-7.208 15.655-15.426 15.655H51.591c-6.756 0-12.346 5.783-12.346 12.549v23.515c0 6.691 5.818 10.628 12.346 12.547 7.816 2.297 15.312 2.713 24.665 0 6.216-1.801 12.346-5.423 12.346-12.547v-9.412H63.938v-3.138h37.012c7.176 0 9.852-5.005 12.348-12.519 2.578-7.735 2.467-15.174 0-25.096-1.774-7.145-5.161-12.521-12.348-12.521h-9.268zM77.809 87.927c2.561 0 4.634 2.097 4.634 4.692 0 2.602-2.074 4.719-4.634 4.719-2.55 0-4.633-2.117-4.633-4.719 0-2.595 2.083-4.692 4.633-4.692z" transform="translate(0 10.26)"/><radialGradient id="python-original-c" cx="1825.678" cy="444.45" r="26.743" gradientTransform="matrix(0 -.24 -1.055 0 532.979 557.576)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#B8B8B8" stop-opacity=".498"/><stop offset="1" stop-color="#7F7F7F" stop-opacity="0"/></radialGradient><path opacity=".444" fill="url(#python-original-c)" d="M97.309 119.597c0 3.543-14.816 6.416-33.091 6.416-18.276 0-33.092-2.873-33.092-6.416 0-3.544 14.815-6.417 33.092-6.417 18.275 0 33.091 2.872 33.091 6.417z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 40 40" style="enable-background:new 0 0 40 40;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FF6161;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M10,25.8V30L0,20l2.1-2.1L10,25.8z M14.2,30H10l10,10l2.1-2.1L14.2,30z M37.9,22.1L40,20L20,0l-2.1,2.1
|
||||
l7.9,7.9H21l-5.5-5.5l-2.1,2.1l3.4,3.4h-2.4v15.5H30v-2.4l3.4,3.4l2.1-2.1L30,19v-4.8L37.9,22.1z M11,9L9,11.1l2.2,2.2l2.1-2.1
|
||||
L11,9z M28.8,26.7l-2.1,2.1l2.2,2.2L31,29L28.8,26.7z M6.6,13.4l-2.1,2.1l5.5,5.5v-4.2L6.6,13.4z M23.1,30H19l5.5,5.5l2.1-2.1
|
||||
L23.1,30z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 679 B |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.7095 0.541777C10.543 0.47519 10.3765 0.541777 10.2767 0.708245C9.81054 1.50729 8.94491 2.03998 7.94611 2.03998C6.9473 2.03998 6.08167 1.50729 5.61556 0.674952C5.51568 0.541778 5.34922 0.475191 5.18275 0.508484C4.31712 0.808126 3.51808 1.27423 2.81891 1.87352C2.68574 1.9734 2.65244 2.17316 2.75232 2.33963C3.21843 3.13867 3.28502 4.17077 2.78562 5.0031C2.28622 5.86874 1.38729 6.33484 0.455073 6.33484C0.288606 6.33484 0.122139 6.46802 0.122139 6.63449C-0.0443288 7.53341 -0.0110333 8.46563 0.155434 9.36455C0.188728 9.53102 0.321903 9.66419 0.521664 9.66419C1.45388 9.66419 2.35281 10.1303 2.85221 10.9959C3.35161 11.8616 3.31832 12.8604 2.85221 13.6594C2.75233 13.7926 2.78562 13.9923 2.91879 14.1255C3.25172 14.4252 3.65125 14.6915 4.05078 14.9246C4.4503 15.1576 4.88311 15.3574 5.31592 15.5238C5.48239 15.5904 5.64885 15.5238 5.74873 15.3574C6.21484 14.5583 7.08047 14.0256 8.07928 14.0256C9.07808 14.0256 9.94371 14.5583 10.4098 15.3907C10.5097 15.5238 10.6762 15.5904 10.8426 15.5571C11.7083 15.2575 12.5073 14.7914 13.2065 14.1921C13.3397 14.0922 13.373 13.8925 13.2731 13.7593C12.807 12.9602 12.7404 11.9282 13.2398 11.0958C13.7392 10.2302 14.6381 9.76407 15.5703 9.76407C15.7368 9.76407 15.9032 9.6309 15.9032 9.46443C16.0697 8.56551 16.0364 7.63329 15.87 6.73437C15.8367 6.5679 15.7035 6.43473 15.5037 6.43473C14.5715 6.43473 13.6726 5.96862 13.1732 5.10299C12.6738 4.23736 12.7071 3.23855 13.1732 2.43951C13.2731 2.30633 13.2398 2.10657 13.1066 1.9734C12.7737 1.67376 12.3741 1.40741 11.9746 1.17435C11.5418 0.874713 11.1423 0.708245 10.7095 0.541777Z" fill="url(#paint0_linear_1825_17920)"/>
|
||||
<path d="M8.0127 12.166C10.2218 12.166 12.0127 10.3752 12.0127 8.16602C12.0127 5.95688 10.2218 4.16602 8.0127 4.16602C5.80356 4.16602 4.0127 5.95688 4.0127 8.16602C4.0127 10.3752 5.80356 12.166 8.0127 12.166Z" fill="url(#paint1_linear_1825_17920)"/>
|
||||
<path d="M8.01237 10.8333C9.48513 10.8333 10.679 9.63943 10.679 8.16667C10.679 6.69391 9.48513 5.5 8.01237 5.5C6.53961 5.5 5.3457 6.69391 5.3457 8.16667C5.3457 9.63943 6.53961 10.8333 8.01237 10.8333Z" fill="url(#paint2_linear_1825_17920)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1825_17920" x1="11.9746" y1="14.9328" x2="3.98418" y2="1.09299" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#626F7A"/>
|
||||
<stop offset="1" stop-color="#8B9299"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1825_17920" x1="10.013" y1="11.6441" x2="6.01236" y2="4.71475" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white"/>
|
||||
<stop offset="1" stop-color="#CCCCCC"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_1825_17920" x1="9.3459" y1="10.4899" x2="6.67884" y2="5.8703" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#114A8B"/>
|
||||
<stop offset="1" stop-color="#0669BC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
54
src/modules/cmdpal/ext/MIcrosoft.CmdPal.Ext.Actions/Icons.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Actions;
|
||||
|
||||
internal sealed class Icons
|
||||
{
|
||||
internal static IconInfo CopilotSvg { get; } = IconHelpers.FromRelativePath("Assets\\Microsoft_Copilot_Icon.svg");
|
||||
|
||||
internal static IconInfo CopilotPng { get; } = IconHelpers.FromRelativePath("Assets\\Microsoft_Copilot_Icon.png");
|
||||
|
||||
internal static IconInfo ActionsPng { get; } = IconHelpers.FromRelativePath("Assets\\Actions.png");
|
||||
|
||||
// windows settings
|
||||
internal static IconInfo Settings { get; } = IconHelpers.FromRelativePath("Assets\\WindowsSettings.svg");
|
||||
|
||||
// google's icon
|
||||
internal static IconInfo Google { get; } = new IconInfo("https://www.google.com/favicon.ico");
|
||||
|
||||
// raycast's icon
|
||||
internal static IconInfo Raycast { get; } = IconHelpers.FromRelativePath("Assets\\Raycast_idE-hcBj9B_1.png");
|
||||
|
||||
internal static IconInfo Pwsh { get; } = IconHelpers.FromRelativePath("Assets\\Powershell.svg");
|
||||
|
||||
internal static IconInfo Python { get; } = IconHelpers.FromRelativePath("Assets\\Python.svg");
|
||||
|
||||
internal static IconInfo Bash { get; } = IconHelpers.FromRelativePath("Assets\\Bash.svg");
|
||||
|
||||
// google calendar's icon
|
||||
internal static IconInfo GoogleCalendar { get; } = new IconInfo("https://upload.wikimedia.org/wikipedia/commons/a/a5/Google_Calendar_icon_%282020%29.svg");
|
||||
|
||||
// bing's icon
|
||||
internal static IconInfo Bing { get; } = new IconInfo("https://www.bing.com/sa/simg/favicon-2x.ico");
|
||||
|
||||
// Action input icons
|
||||
internal static IconInfo DocumentInput { get; } = new IconInfo("Assets\\Document.png");
|
||||
|
||||
internal static IconInfo FileInput { get; } = new IconInfo("\uE8A5");
|
||||
|
||||
internal static IconInfo PhotoInput { get; } = new IconInfo("\uE91b");
|
||||
|
||||
internal static IconInfo TextInput { get; } = new IconInfo("\uE710");
|
||||
|
||||
internal static IconInfo StreamingTextInput { get; } = new IconInfo("\uE710");
|
||||
|
||||
internal static IconInfo RemoteFileInput { get; } = new IconInfo("\uE8E5");
|
||||
|
||||
internal static IconInfo TableInput { get; } = new IconInfo("\uf575");
|
||||
|
||||
internal static IconInfo ContactInput { get; } = new IconInfo("\uE77b");
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.Actions</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<!-- MRT from windows app sdk will search for a pri file with the same name of the module before defaulting to resources.pri -->
|
||||
<ProjectPriFileName>Microsoft.CmdPal.Ext.Actions.pri</ProjectPriFileName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Resources.Designer.cs">
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Update="Assets\Microsoft_Copilot_Icon.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Microsoft_Copilot_Icon.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<!-- <Content Update="Assets\Actions.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content> -->
|
||||
<!-- <Content Update="Assets\WindowsSettings.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content> -->
|
||||
<Content Update="Assets\Raycast_idE-hcBj9B_1.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Document.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Powershell.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Python.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Bash.svg">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Assets\Microsoft_Copilot_Icon.svg" />
|
||||
<None Remove="Assets\Microsoft_Copilot_Icon.png" />
|
||||
<!-- <None Remove="Assets\Actions.png" /> -->
|
||||
<!-- <None Remove="Assets\WindowsSettings.svg" /> -->
|
||||
<None Remove="Assets\Raycast_idE-hcBj9B_1.png" />
|
||||
<None Remove="Assets\Document.png" />
|
||||
<None Remove="Assets\Powershell.svg" />
|
||||
<None Remove="Assets\Python.svg" />
|
||||
<None Remove="Assets\Bash.svg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Properties\Resources.resx">
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,366 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.AI.Actions;
|
||||
using Windows.AI.Actions.Hosting;
|
||||
using Windows.ApplicationModel.Contacts;
|
||||
using Windows.Storage.Pickers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Actions;
|
||||
|
||||
internal sealed partial class ActionsTestPage : ListPage
|
||||
{
|
||||
private readonly ActionRuntime _actionRuntime;
|
||||
|
||||
public ActionsTestPage(ActionRuntime actionRuntime)
|
||||
{
|
||||
_actionRuntime = actionRuntime;
|
||||
Icon = Icons.ActionsPng;
|
||||
Title = "Actions";
|
||||
Name = "Open";
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
var actions = _actionRuntime.ActionCatalog.GetAllActions();
|
||||
|
||||
var items = new List<ListItem>();
|
||||
foreach (var action in actions)
|
||||
{
|
||||
foreach (var overload in action.GetOverloads())
|
||||
{
|
||||
try
|
||||
{
|
||||
var inputs = overload.GetInputs();
|
||||
|
||||
var tags = inputs.AsEnumerable().Select(input => new Tag(input.Name) { Icon = GetIconForInput(input)! }).ToList();
|
||||
var command = new DoActionCommand(action.Id, overload, _actionRuntime) { Name = "Invoke" };
|
||||
items.Add(new ListItem(command)
|
||||
{
|
||||
Title = action.Description,
|
||||
Subtitle = overload.DescriptionTemplate,
|
||||
Icon = new IconInfo(action.IconFullPath),
|
||||
Tags = tags.ToArray(),
|
||||
});
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ExtensionHost.LogMessage($"Unsupported action {overload.DescriptionTemplate}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items.ToArray();
|
||||
}
|
||||
|
||||
private static IconInfo? GetIconForInput(ActionEntityRegistrationInfo input)
|
||||
{
|
||||
return input.Kind switch
|
||||
{
|
||||
ActionEntityKind.None => null,
|
||||
ActionEntityKind.Document => Icons.DocumentInput,
|
||||
ActionEntityKind.File => Icons.FileInput,
|
||||
ActionEntityKind.Photo => Icons.PhotoInput,
|
||||
ActionEntityKind.Text => Icons.TextInput,
|
||||
ActionEntityKind.StreamingText => Icons.StreamingTextInput,
|
||||
ActionEntityKind.RemoteFile => Icons.RemoteFileInput,
|
||||
ActionEntityKind.Table => Icons.TableInput,
|
||||
ActionEntityKind.Contact => Icons.ContactInput,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
public partial class CommandParameter : BaseObservable, ICommandArgument
|
||||
{
|
||||
public virtual string Name { get; set; }
|
||||
|
||||
public virtual bool Required { get; set; }
|
||||
|
||||
public virtual ParameterType Type { get; set; }
|
||||
|
||||
public virtual object? Value
|
||||
{
|
||||
get; set
|
||||
{
|
||||
if (field != value)
|
||||
{
|
||||
field = value;
|
||||
OnPropertyChanged(nameof(Value));
|
||||
OnPropertyChanged(nameof(DisplayName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual string? DisplayName => Value?.ToString() ?? string.Empty;
|
||||
|
||||
public virtual IIconInfo? Icon
|
||||
{
|
||||
get => field;
|
||||
set
|
||||
{
|
||||
field = value;
|
||||
OnPropertyChanged(nameof(Icon));
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ShowPicker(ulong hostHwnd)
|
||||
{
|
||||
}
|
||||
|
||||
public CommandParameter(string name = "", bool required = true, ParameterType type = ParameterType.Text)
|
||||
{
|
||||
Name = name;
|
||||
Required = required;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
public abstract partial class InvokableWithParams : Command, IInvokableCommandWithParameters
|
||||
{
|
||||
public ICommandArgument[] Parameters { get; set; } = [];
|
||||
|
||||
public abstract ICommandResult InvokeWithArgs(object sender, ICommandArgument[] args);
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
public partial class DoActionCommand : InvokableWithParams
|
||||
{
|
||||
private readonly ActionOverload _action;
|
||||
private readonly ActionRuntime _actionRuntime;
|
||||
private readonly string _id;
|
||||
|
||||
public override ICommandResult InvokeWithArgs(object sender, ICommandArgument[] args)
|
||||
{
|
||||
if (args == null)
|
||||
{
|
||||
var error = new ToastStatusMessage("no args oops");
|
||||
error.Show();
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
var s = $"{_action.DescriptionTemplate}(";
|
||||
foreach (var arg in args)
|
||||
{
|
||||
s += $"{arg.Name}: {arg.Value.ToString()}";
|
||||
}
|
||||
|
||||
s += ")";
|
||||
|
||||
// var t = new ToastStatusMessage(s);
|
||||
// t.Show();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var c = _actionRuntime.CreateInvocationContext(actionId: _id);
|
||||
var f = _actionRuntime.EntityFactory;
|
||||
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
var arg = args[i];
|
||||
var inputParam = _action.GetInputs()[i];
|
||||
var entity = CreateEntity(inputParam, f, arg);
|
||||
c.SetInputEntity(arg.Name, entity);
|
||||
}
|
||||
|
||||
// foreach (var i in args)
|
||||
// {
|
||||
// var v = f.CreatePhotoEntity(i.Value as string);
|
||||
// c.SetInputEntity(i.Name, v);
|
||||
// }
|
||||
var task = _action.InvokeAsync(c);
|
||||
await task;
|
||||
var status = task.Status;
|
||||
var statusType = c.Result switch
|
||||
{
|
||||
ActionInvocationResult.Success => MessageState.Success,
|
||||
_ => MessageState.Error,
|
||||
};
|
||||
var text = c.Result switch
|
||||
{
|
||||
ActionInvocationResult.Success => $"{c.Result.ToString()}",
|
||||
_ => $"{c.Result.ToString()}: {c.ExtendedError}",
|
||||
};
|
||||
var resultToast = new ToastStatusMessage(new StatusMessage() { Message = text, State = statusType });
|
||||
resultToast.Show();
|
||||
});
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
private ActionEntity CreateEntity(ActionEntityRegistrationInfo i, ActionEntityFactory f, ICommandArgument arg)
|
||||
{
|
||||
ActionEntity v = i.Kind switch
|
||||
{
|
||||
ActionEntityKind.Photo => f.CreatePhotoEntity(arg.Value as string),
|
||||
ActionEntityKind.Document => f.CreateDocumentEntity(arg.Value as string),
|
||||
ActionEntityKind.File => f.CreateFileEntity(arg.Value as string),
|
||||
ActionEntityKind.Text => f.CreateTextEntity(arg.Value as string),
|
||||
ActionEntityKind.Contact => f.CreateContactEntity(arg.Value as Contact),
|
||||
_ => throw new NotSupportedException($"Unsupported entity kind: {i.Kind}"),
|
||||
};
|
||||
return v;
|
||||
}
|
||||
|
||||
public DoActionCommand(string actionId, ActionOverload action, ActionRuntime actionRuntime)
|
||||
{
|
||||
_action = action;
|
||||
|
||||
var inputs = action.GetInputs();
|
||||
|
||||
_actionRuntime = actionRuntime;
|
||||
_id = actionId;
|
||||
|
||||
// ICommandArgument[] commandParameters = inputs.AsEnumerable()
|
||||
// .Select(input => new CommandParameter(input.Name))
|
||||
// .ToArray();
|
||||
// Parameters = commandParameters;
|
||||
foreach (var input in inputs)
|
||||
{
|
||||
var param = input.Kind switch
|
||||
{
|
||||
ActionEntityKind.None => new CommandParameter(input.Name),
|
||||
ActionEntityKind.Document => new CommandParameter(input.Name),
|
||||
ActionEntityKind.File => new CommandParameter(input.Name),
|
||||
ActionEntityKind.Photo => new ImageParameter(input.Name),
|
||||
ActionEntityKind.Text => new CommandParameter(input.Name),
|
||||
|
||||
// ActionEntityKind.StreamingText => new CommandParameter(input.Name, input.Required, ParameterType.StreamingText),
|
||||
// ActionEntityKind.RemoteFile => new CommandParameter(input.Name, input.Required, ParameterType.RemoteFile),
|
||||
// ActionEntityKind.Table => new CommandParameter(input.Name, input.Required, ParameterType.Table),
|
||||
ActionEntityKind.Contact => new ContactParameter(input.Name),
|
||||
_ => throw new NotSupportedException($"Unsupported action entity kind: {input.Kind}"),
|
||||
};
|
||||
|
||||
// var parameter = new CommandParameter(input.Name, input.Required, input.Kind.ToParameterType());
|
||||
// if (input.DefaultValue != null)
|
||||
// {
|
||||
// parameter.Value = input.DefaultValue;
|
||||
// }
|
||||
Parameters = Parameters.Append(param).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
public partial class ImageParameter : CommandParameter
|
||||
{
|
||||
private string? _filePath;
|
||||
|
||||
public ImageParameter(string name = "", bool required = true)
|
||||
: base(name, required, ParameterType.Custom)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ShowPicker(ulong hostHwnd)
|
||||
{
|
||||
var picker = new FileOpenPicker
|
||||
{
|
||||
SuggestedStartLocation = PickerLocationId.PicturesLibrary,
|
||||
};
|
||||
picker.FileTypeFilter.Add(".jpg");
|
||||
picker.FileTypeFilter.Add(".jpeg");
|
||||
picker.FileTypeFilter.Add(".png");
|
||||
picker.FileTypeFilter.Add(".gif");
|
||||
picker.FileTypeFilter.Add(".bmp");
|
||||
picker.FileTypeFilter.Add(".tiff");
|
||||
picker.FileTypeFilter.Add(".webp");
|
||||
|
||||
// Initialize the picker with the window handle
|
||||
WinRT.Interop.InitializeWithWindow.Initialize(picker, (IntPtr)hostHwnd);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = await picker.PickSingleFileAsync();
|
||||
if (file != null)
|
||||
{
|
||||
_filePath = file.Path;
|
||||
Value = _filePath;
|
||||
Icon = new IconInfo(_filePath);
|
||||
|
||||
// TODO! update display name
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle any exceptions that might occur during file picking
|
||||
System.Diagnostics.Debug.WriteLine($"Error picking image file: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override string? DisplayName
|
||||
{
|
||||
get { return string.IsNullOrEmpty(_filePath) ? null : Path.GetFileName(_filePath); }
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
public partial class ContactParameter : CommandParameter
|
||||
{
|
||||
private Contact? _selectedContact;
|
||||
|
||||
public ContactParameter(string name = "", bool required = true)
|
||||
: base(name, required, ParameterType.Custom)
|
||||
{
|
||||
}
|
||||
|
||||
public override void ShowPicker(ulong hostHwnd)
|
||||
{
|
||||
var picker = new ContactPicker
|
||||
{
|
||||
// CommitButtonText = "Select",
|
||||
|
||||
// SelectionMode = ContactSelectionMode.Contacts,
|
||||
};
|
||||
|
||||
// Specify which contact properties to retrieve
|
||||
// picker.DesiredFieldsWithContactFieldType.Add(ContactFieldType.Email);
|
||||
// picker.DesiredFieldsWithContactFieldType.Add(ContactFieldType.PhoneNumber);
|
||||
|
||||
// Initialize the picker with the window handle
|
||||
WinRT.Interop.InitializeWithWindow.Initialize(picker, (IntPtr)hostHwnd);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var contact = await picker.PickContactAsync();
|
||||
if (contact != null)
|
||||
{
|
||||
_selectedContact = contact;
|
||||
Value = contact;
|
||||
Icon = Icons.ContactInput;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Handle any exceptions that might occur during contact picking
|
||||
System.Diagnostics.Debug.WriteLine($"Error picking contact: {ex.Message}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override string? DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
return _selectedContact == null
|
||||
? null
|
||||
: !string.IsNullOrEmpty(_selectedContact.DisplayName)
|
||||
? _selectedContact.DisplayName
|
||||
: _selectedContact.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// 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.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Actions;
|
||||
|
||||
internal sealed partial class AgentsTestPage : ListPage
|
||||
{
|
||||
private readonly List<ListItem> _agentItems;
|
||||
private readonly List<AgentInfo> _agentInfos =
|
||||
[
|
||||
new AgentInfo("@copilot", "Ask Windows Copilot anything...", "Windows Copilot", Icons.CopilotPng),
|
||||
new AgentInfo("@m365", "Ask Microsoft 365 Copilot...", "Microsoft 365 Copilot", Icons.CopilotSvg),
|
||||
new AgentInfo("@settings", "Open Windows Settings...", "Windows Settings", Icons.Settings),
|
||||
new AgentInfo("@google", "Ask Google...", "Google", Icons.Google),
|
||||
new AgentInfo("@bing", "Ask Bing...", "Bing", Icons.Bing),
|
||||
new AgentInfo("@google-calendar", "Ask Google Calendar...", "Google Calendar", Icons.GoogleCalendar)
|
||||
];
|
||||
|
||||
public AgentsTestPage()
|
||||
{
|
||||
Icon = Icons.CopilotSvg;
|
||||
Title = "Agents";
|
||||
Name = "Open";
|
||||
|
||||
_agentItems = _agentInfos.ConvertAll(agentInfo =>
|
||||
new ListItem(new AgentQueryPage(agentInfo))
|
||||
{
|
||||
Title = agentInfo.Handle,
|
||||
Subtitle = agentInfo.Subtitle,
|
||||
Icon = agentInfo.Icon,
|
||||
});
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return _agentItems.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
internal sealed class AgentInfo
|
||||
{
|
||||
public string Handle { get; }
|
||||
|
||||
public string Prompt { get; }
|
||||
|
||||
public string Subtitle { get; }
|
||||
|
||||
public IconInfo Icon { get; }
|
||||
|
||||
public AgentInfo(string handle, string prompt, string subtitle, IconInfo icon)
|
||||
{
|
||||
Handle = handle;
|
||||
Prompt = prompt;
|
||||
Subtitle = subtitle;
|
||||
Icon = icon;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
internal sealed partial class AgentQueryPage : DynamicListPage
|
||||
{
|
||||
private readonly List<ListItem> _items = [];
|
||||
private readonly string _agentSubtitle;
|
||||
|
||||
private readonly AgentInfo _agentInfo;
|
||||
|
||||
public AgentQueryPage(AgentInfo agentInfo)
|
||||
{
|
||||
_agentInfo = agentInfo;
|
||||
Icon = agentInfo.Icon;
|
||||
Title = agentInfo.Handle;
|
||||
Name = "Open";
|
||||
_agentSubtitle = agentInfo.Subtitle;
|
||||
EmptyContent = new CommandItem()
|
||||
{
|
||||
Command = new NoOpCommand(),
|
||||
Title = agentInfo.Prompt,
|
||||
Subtitle = _agentSubtitle,
|
||||
Icon = agentInfo.Icon,
|
||||
};
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
return _items.ToArray();
|
||||
}
|
||||
|
||||
public override void UpdateSearchText(string oldSearch, string newSearch)
|
||||
{
|
||||
// If the new search text is empty, we can reset the page to its initial state.
|
||||
if (string.IsNullOrEmpty(newSearch))
|
||||
{
|
||||
_items.Clear();
|
||||
RaiseItemsChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we don't have an item yet, create it. Otherwise, update the existing item.
|
||||
if (_items.Count == 0)
|
||||
{
|
||||
_items.Add(new ListItem(new AgentQueryContentPage(_agentInfo, newSearch))
|
||||
{
|
||||
Title = newSearch,
|
||||
Subtitle = _agentSubtitle,
|
||||
Icon = this.Icon,
|
||||
});
|
||||
RaiseItemsChanged();
|
||||
}
|
||||
else
|
||||
{
|
||||
_items[0].Title = newSearch;
|
||||
(_items[0].Command as AgentQueryContentPage)?.UpdateQuery(newSearch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now, a content page that will be used to display the results of the agent query
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
internal sealed partial class AgentQueryContentPage : ContentPage
|
||||
{
|
||||
private readonly List<IContent> _content = [];
|
||||
private readonly AgentInfo _agentInfo;
|
||||
private readonly List<string> _responses =
|
||||
[
|
||||
"This is a response from the agent.",
|
||||
"You can ask more questions or refine your query.",
|
||||
"Here are some suggestions based on your query.",
|
||||
"Would you like to know more about this topic?",
|
||||
"Feel free to ask anything else!"
|
||||
];
|
||||
|
||||
private bool _started;
|
||||
private Task? _contentGenerationTask;
|
||||
|
||||
private int _responsesGenerated;
|
||||
|
||||
private string _query = string.Empty;
|
||||
|
||||
public AgentQueryContentPage(AgentInfo agentInfo, string query)
|
||||
{
|
||||
Title = agentInfo.Handle;
|
||||
Icon = agentInfo.Icon;
|
||||
Name = "Ask Agent";
|
||||
IsLoading = true;
|
||||
_query = query;
|
||||
_agentInfo = agentInfo;
|
||||
}
|
||||
|
||||
public void UpdateQuery(string query)
|
||||
{
|
||||
_query = query;
|
||||
_responsesGenerated = 0;
|
||||
_content.Clear();
|
||||
_started = false;
|
||||
Title = $"{_agentInfo.Handle}";
|
||||
|
||||
// RaiseItemsChanged();
|
||||
}
|
||||
|
||||
public override IContent[] GetContent()
|
||||
{
|
||||
if (!_started)
|
||||
{
|
||||
_content.Add(new MarkdownContent($"## {_agentInfo.Handle}\n\n{_query}"));
|
||||
|
||||
// Kick off a task to generate the content if it hasn't been started yet.
|
||||
_contentGenerationTask = Task.Run(() =>
|
||||
{
|
||||
for (var i = 0; i < _responses.Count; i++)
|
||||
{
|
||||
// Simulate generating a response.
|
||||
Task.Delay(1000).Wait();
|
||||
_content.Add(new MarkdownContent(_responses[i]));
|
||||
_responsesGenerated++;
|
||||
Title = $"{_agentInfo.Handle} ({_responsesGenerated})";
|
||||
RaiseItemsChanged();
|
||||
}
|
||||
|
||||
IsLoading = false;
|
||||
|
||||
var toast = new ToastStatusMessage(new StatusMessage
|
||||
{
|
||||
Message = $"Did the thing",
|
||||
State = MessageState.Success,
|
||||
});
|
||||
toast.Show();
|
||||
});
|
||||
|
||||
_started = true;
|
||||
_contentGenerationTask.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
return _content.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.ApplicationModel.Contacts;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Actions;
|
||||
|
||||
internal sealed partial class ContactsListPage : ListPage
|
||||
{
|
||||
private List<ListItem> _contactItems = [];
|
||||
private bool _hasLoadedContacts;
|
||||
|
||||
public ContactsListPage()
|
||||
{
|
||||
Icon = Icons.ContactInput;
|
||||
Title = "Contacts";
|
||||
Name = "Contacts";
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
if (!_hasLoadedContacts)
|
||||
{
|
||||
_ = LoadContactsAsync();
|
||||
}
|
||||
|
||||
return _contactItems.ToArray();
|
||||
}
|
||||
|
||||
private async Task LoadContactsAsync()
|
||||
{
|
||||
if (_hasLoadedContacts)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_hasLoadedContacts = true;
|
||||
|
||||
// Request access to contacts
|
||||
var accessStatus = await ContactManager.RequestStoreAsync(ContactStoreAccessType.AllContactsReadOnly);
|
||||
if (accessStatus == null)
|
||||
{
|
||||
// No access to contacts
|
||||
EmptyContent = new CommandItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Unable to access contacts",
|
||||
Subtitle = "Contact access is not available",
|
||||
Icon = Icons.ContactInput,
|
||||
};
|
||||
RaiseItemsChanged(_contactItems.Count);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get all contacts
|
||||
var contacts = await accessStatus.FindContactsAsync();
|
||||
|
||||
_contactItems.Clear();
|
||||
|
||||
if (contacts == null || !contacts.Any())
|
||||
{
|
||||
EmptyContent = new CommandItem(new NoOpCommand())
|
||||
{
|
||||
Title = "No Contacts Found",
|
||||
Subtitle = "You have no contacts available.",
|
||||
Icon = Icons.ContactInput,
|
||||
};
|
||||
RaiseItemsChanged(_contactItems.Count);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var contact in contacts)
|
||||
{
|
||||
var contactName = GetContactDisplayName(contact);
|
||||
var contactEmail = GetContactEmail(contact);
|
||||
|
||||
if (!string.IsNullOrEmpty(contactName))
|
||||
{
|
||||
var contactCommand = new ContactCommand(contact);
|
||||
|
||||
_contactItems.Add(new ListItem(contactCommand)
|
||||
{
|
||||
Title = contactName,
|
||||
Subtitle = contactEmail ?? string.Empty,
|
||||
Icon = Icons.ContactInput,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort contacts by name
|
||||
_contactItems = _contactItems.OrderBy(item => item.Title).ToList();
|
||||
|
||||
// Notify that items have changed
|
||||
RaiseItemsChanged(_contactItems.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_contactItems.Clear();
|
||||
EmptyContent = new CommandItem(new NoOpCommand())
|
||||
{
|
||||
Title = "Error loading contacts",
|
||||
Subtitle = $"Failed to load contacts: {ex.Message}",
|
||||
Icon = Icons.ContactInput,
|
||||
};
|
||||
RaiseItemsChanged(_contactItems.Count);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetContactDisplayName(Contact contact)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(contact.DisplayName))
|
||||
{
|
||||
return contact.DisplayName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(contact.Name))
|
||||
{
|
||||
return contact.Name;
|
||||
}
|
||||
|
||||
// Try to get name from first and last name
|
||||
if (!string.IsNullOrEmpty(contact.FirstName) || !string.IsNullOrEmpty(contact.LastName))
|
||||
{
|
||||
return $"{contact.FirstName} {contact.LastName}".Trim();
|
||||
}
|
||||
|
||||
return "Unknown Contact";
|
||||
}
|
||||
|
||||
private static string? GetContactEmail(Contact contact)
|
||||
{
|
||||
var email = contact.Emails?.FirstOrDefault();
|
||||
return email?.Address;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "Helper command class")]
|
||||
public partial class ContactCommand : InvokableCommand
|
||||
{
|
||||
private readonly Contact _contact;
|
||||
|
||||
public ContactCommand(Contact contact)
|
||||
{
|
||||
_contact = contact;
|
||||
Name = "View Contact";
|
||||
Icon = Icons.ContactInput;
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
var contactName = !string.IsNullOrEmpty(_contact.DisplayName)
|
||||
? _contact.DisplayName
|
||||
: !string.IsNullOrEmpty(_contact.Name)
|
||||
? _contact.Name
|
||||
: "Unknown Contact";
|
||||
|
||||
var message = new ToastStatusMessage($"Selected contact: {contactName}");
|
||||
message.Show();
|
||||
|
||||
return CommandResult.Dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,666 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Actions;
|
||||
|
||||
internal sealed partial class ScriptsTestPage : ListPage
|
||||
{
|
||||
private readonly Settings _settings = new();
|
||||
|
||||
public ScriptsTestPage()
|
||||
{
|
||||
Icon = Icons.Raycast;
|
||||
Title = "Scripts";
|
||||
Name = "Open";
|
||||
ShowDetails = true;
|
||||
}
|
||||
|
||||
public override IListItem[] GetItems()
|
||||
{
|
||||
var files = GetScriptFiles(_settings.ScriptsPath);
|
||||
var metadata = GetAllScriptMetadata(files, _settings);
|
||||
|
||||
var commandItems = GetAllCommandItems(metadata.OrderBy(m => m.PackageName), _settings);
|
||||
|
||||
return commandItems.ToArray();
|
||||
}
|
||||
|
||||
private static string[] GetScriptFiles(string scriptsPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(scriptsPath) || !Directory.Exists(scriptsPath))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
// Get all script files in the directory and subdirectories
|
||||
// We are looking for .sh, .ps1, and .py files
|
||||
if (!Directory.Exists(scriptsPath))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var files = Directory.GetFiles(scriptsPath, "*.*", SearchOption.AllDirectories)
|
||||
.Where(f => f.EndsWith(".sh", StringComparison.OrdinalIgnoreCase) ||
|
||||
f.EndsWith(".ps1", StringComparison.OrdinalIgnoreCase) ||
|
||||
f.EndsWith(".py", StringComparison.OrdinalIgnoreCase))
|
||||
.ToArray();
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private static ScriptMetadata? GetScriptMetadata(string scriptFile)
|
||||
{
|
||||
if (string.IsNullOrEmpty(scriptFile) || !File.Exists(scriptFile))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(scriptFile).ToLowerInvariant();
|
||||
return ext switch
|
||||
{
|
||||
".sh" => ScriptMetadata.FromBash(scriptFile),
|
||||
".ps1" => ScriptMetadata.FromPowershell(scriptFile),
|
||||
".py" => ScriptMetadata.FromPython(scriptFile),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
private static ScriptMetadata[] GetAllScriptMetadata(string[] scriptFiles, Settings settings)
|
||||
{
|
||||
var metadataList = new List<ScriptMetadata>();
|
||||
|
||||
foreach (var scriptFile in scriptFiles)
|
||||
{
|
||||
var metadata = GetScriptMetadata(scriptFile);
|
||||
if (metadata != null)
|
||||
{
|
||||
metadataList.Add(metadata);
|
||||
}
|
||||
}
|
||||
|
||||
return metadataList.ToArray();
|
||||
}
|
||||
|
||||
private static ListItem[] GetAllCommandItems(IEnumerable<ScriptMetadata> metadata, Settings settings)
|
||||
{
|
||||
var commandItems = new List<ListItem>();
|
||||
|
||||
foreach (var script in metadata)
|
||||
{
|
||||
if (script == null || string.IsNullOrEmpty(script.Title))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var command = script.ToCommand(settings);
|
||||
var scriptPage = new MarkdownPage($"```\r\n{script.ScriptBody}\r\n```")
|
||||
{
|
||||
Title = script.Title,
|
||||
Icon = script.IconInfo,
|
||||
Name = "View script",
|
||||
};
|
||||
var viewScript = new CommandContextItem(scriptPage)
|
||||
{
|
||||
};
|
||||
|
||||
var commandItem = new ListItem(command)
|
||||
{
|
||||
Title = script.Title,
|
||||
Subtitle = script.PackageName ?? string.Empty,
|
||||
Icon = script.IconInfo,
|
||||
|
||||
// Details = new Details() { Body = $"```\r\n{script.ScriptBody}\r\n```" },
|
||||
MoreCommands = [viewScript],
|
||||
|
||||
// Tags = script.Arguments
|
||||
// .Where(arg => arg != null && !string.IsNullOrEmpty(arg.Placeholder))
|
||||
// .Select(arg => new Tag(arg!.Placeholder))
|
||||
// .ToArray(),
|
||||
Tags = [script.LanguageTag, new Tag(script.Mode.ToString())],
|
||||
};
|
||||
|
||||
commandItems.Add(commandItem);
|
||||
}
|
||||
|
||||
return commandItems.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
internal sealed class Settings
|
||||
{
|
||||
public string ScriptsPath { get; set; } = "d:\\dev\\script-commands-test\\windows-commands";
|
||||
|
||||
public string BashPath { get; set; } = "wsl -- bash";
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
internal sealed partial class ScriptArgument
|
||||
{
|
||||
public string Type { get; set; } = "text";
|
||||
|
||||
public string Placeholder { get; set; } = string.Empty;
|
||||
|
||||
public bool Optional { get; set; }
|
||||
|
||||
public bool PercentEncoded { get; set; }
|
||||
|
||||
public DropdownItem[]? Data { get; set; }
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
internal sealed partial class DropdownItem
|
||||
{
|
||||
public string Title { get; set; } = string.Empty;
|
||||
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
internal sealed partial class ScriptMetadata
|
||||
{
|
||||
/*
|
||||
|
||||
From the README
|
||||
|
||||
| Name | Description | Required | App Version |
|
||||
|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|---------------------|
|
||||
|schemaVersion | Schema version to prepare for future changes in the API. Currently there is only version 1 available. | Yes | 0.29+ |
|
||||
| title | Display name of the Script Command that is shown as title in the root search. | Yes | 0.29+ |
|
||||
| mode | Specifies how the script is executed and how the output is presented. [Details of the options for this parameter can be viewed here](https://github.com/raycast/script-commands/blob/master/documentation/OUTPUTMODES.md) | Yes | 0.29+ |
|
||||
| packageName | Display name of the package that is shown as subtitle in the root search. When not provided, the name will be inferred from the script directory name. | No | 0.29+ |
|
||||
| icon | Icon that is displayed in the root search. Can be an emoji, a file path (relative or full) or a remote URL (only https). Supported formats for images are PNG and JPEG. Please make sure to use small icons, recommended size - 64px. | No | 0.29+ |
|
||||
| iconDark | Same as `icon`, but for dark theme. If not specified, then `icon` will be used in both themes. | No | 1.3.0+ |
|
||||
| currentDirectoryPath | Path from which the script is executed. Default is the path of the script. | No | 0.29+ |
|
||||
| needsConfirmation | Specify `true` if you would like to show confirmation alert dialog before running the script. Can be helpful with destructive scripts like "Quit All Apps" or "Empty Trash". Default value is `false`. | No | 0.30+ |
|
||||
| refreshTime | Specify a refresh interval for inline mode scripts in seconds, minutes, hours or days. Examples: 10s, 1m, 12h, 1d. Note that the actual times can vary depending on how the OS prioritises scheduled work. The minimum refresh interval is 10 seconds. If you have more than 10 inline commands, only the first 10 will be refreshed automatically; the rest have to be manually refreshed by navigating to them and pressing `return`.| No | 0.31+ |
|
||||
| argument[1...3] | [Custom arguments, see Passing Arguments page](https://github.com/raycast/script-commands/blob/master/documentation/ARGUMENTS.md) for detail of how to use this field | No | 1.2.0+ |
|
||||
| author | Define an author name to be part of the script commands documentation | No | |
|
||||
| authorURL | Author social media, website, email or anything to help the users to get in touch | No | |
|
||||
| description | A brief description about the script command to be presented in the documentation | No | |
|
||||
|
||||
*/
|
||||
public string? SchemaVersion { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
|
||||
public ScriptMode Mode { get; set; }
|
||||
|
||||
public string? PackageName { get; set; }
|
||||
|
||||
public string? Icon { get; set; }
|
||||
|
||||
public string? IconDark { get; set; }
|
||||
|
||||
public string ScriptFilePath { get; set; } = string.Empty;
|
||||
|
||||
public IconInfo IconInfo => new(
|
||||
new(ResolveIconPath(IconDark ?? Icon ?? string.Empty)),
|
||||
new(ResolveIconPath(Icon ?? IconDark ?? string.Empty)));
|
||||
|
||||
public string? CurrentDirectoryPath { get; set; }
|
||||
|
||||
public bool NeedsConfirmation { get; set; }
|
||||
|
||||
public string? RefreshTime { get; set; }
|
||||
|
||||
// max 3 arguments
|
||||
public ScriptArgument?[] Arguments { get; set; } = new ScriptArgument?[3];
|
||||
|
||||
public string? Author { get; set; }
|
||||
|
||||
public string? AuthorUrl { get; set; }
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public string ScriptBody { get; set; } = string.Empty;
|
||||
|
||||
internal static readonly char[] Separator = new[] { '\n', '\r' };
|
||||
|
||||
public string Language { get; private set; } = string.Empty;
|
||||
|
||||
public static Tag PowerShellTag { get; } = new("ps1") { Icon = Icons.Pwsh };
|
||||
|
||||
public static Tag BashTag { get; } = new("bash") { Icon = Icons.Bash };
|
||||
|
||||
public static Tag PythonTag { get; } = new("py") { Icon = Icons.Python };
|
||||
|
||||
public Tag LanguageTag
|
||||
{
|
||||
get
|
||||
{
|
||||
return Language switch
|
||||
{
|
||||
"ps1" => PowerShellTag,
|
||||
"py" => PythonTag,
|
||||
"bash" => BashTag,
|
||||
_ => new Tag() { Text = Language, Icon = Icons.DocumentInput },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolveIconPath(string iconPath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
// If it's an emoji, URL, or already an absolute path, return as-is
|
||||
if (iconPath.Length <= 2 || // Likely an emoji
|
||||
iconPath.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
|
||||
iconPath.StartsWith("https://", StringComparison.OrdinalIgnoreCase) ||
|
||||
Path.IsPathRooted(iconPath))
|
||||
{
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
// If it's a relative path and we have a script file path, resolve it
|
||||
if (!string.IsNullOrEmpty(ScriptFilePath))
|
||||
{
|
||||
var scriptDirectory = Path.GetDirectoryName(ScriptFilePath);
|
||||
if (!string.IsNullOrEmpty(scriptDirectory))
|
||||
{
|
||||
var resolvedPath = Path.Combine(scriptDirectory, iconPath);
|
||||
|
||||
// Normalize the path and check if the file exists
|
||||
if (File.Exists(resolvedPath))
|
||||
{
|
||||
return Path.GetFullPath(resolvedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the original path if we can't resolve it
|
||||
return iconPath;
|
||||
}
|
||||
|
||||
private static ScriptArgument? ParseArgument(string argumentJson)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(argumentJson))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<ScriptArgument>(argumentJson, JsonSerializationContext.Default.ScriptArgument);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// If JSON parsing fails, treat it as a simple text argument for backward compatibility
|
||||
return new ScriptArgument
|
||||
{
|
||||
Type = "text",
|
||||
Placeholder = argumentJson,
|
||||
Optional = false,
|
||||
PercentEncoded = false,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static ScriptMetadata? FromHashComments(string bashFile, string language = "sh")
|
||||
{
|
||||
if (string.IsNullOrEmpty(bashFile) || !File.Exists(bashFile))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var text = File.ReadAllText(bashFile);
|
||||
|
||||
// Now parse the file looking for the metadata
|
||||
// Metadata is in the form of:
|
||||
// # @raycast.schemaVersion 1
|
||||
// # @raycast.title My First Script
|
||||
var lines = text.Split(Separator, StringSplitOptions.RemoveEmptyEntries);
|
||||
var metadata = new ScriptMetadata
|
||||
{
|
||||
ScriptBody = text,
|
||||
Language = language,
|
||||
ScriptFilePath = bashFile,
|
||||
};
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.StartsWith("# @raycast.", StringComparison.InvariantCulture))
|
||||
{
|
||||
var parts = line.Substring(11).Split(' ', 2);
|
||||
if (parts.Length == 2)
|
||||
{
|
||||
var key = parts[0].Trim();
|
||||
var value = parts[1].Trim();
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case "schemaVersion":
|
||||
metadata.SchemaVersion = value;
|
||||
break;
|
||||
case "title":
|
||||
metadata.Title = value;
|
||||
break;
|
||||
case "mode":
|
||||
metadata.Mode = value switch
|
||||
{
|
||||
"fullOutput" => ScriptMode.FullOutput,
|
||||
"compact" => ScriptMode.Compact,
|
||||
"silent" => ScriptMode.Silent,
|
||||
"inline" => ScriptMode.Inline,
|
||||
_ => ScriptMode.FullOutput, // Default to FullOutput if unknown
|
||||
};
|
||||
|
||||
break;
|
||||
case "packageName":
|
||||
metadata.PackageName = value;
|
||||
break;
|
||||
case "icon":
|
||||
metadata.Icon = value;
|
||||
break;
|
||||
case "iconDark":
|
||||
metadata.IconDark = value;
|
||||
break;
|
||||
case "currentDirectoryPath":
|
||||
metadata.CurrentDirectoryPath = value;
|
||||
break;
|
||||
case "needsConfirmation":
|
||||
metadata.NeedsConfirmation = bool.Parse(value);
|
||||
break;
|
||||
case "refreshTime":
|
||||
metadata.RefreshTime = value;
|
||||
break;
|
||||
|
||||
// case "argument":
|
||||
// if (metadata.Arguments == null)
|
||||
// {
|
||||
// metadata.Arguments = [];
|
||||
// }
|
||||
|
||||
// Array.Resize(ref metadata.Arguments, metadata.Arguments.Length + 1);
|
||||
// metadata.Arguments[^1] = value;
|
||||
// break;
|
||||
case "author":
|
||||
metadata.Author = value;
|
||||
break;
|
||||
case "authorURL":
|
||||
metadata.AuthorUrl = value;
|
||||
break;
|
||||
case "description":
|
||||
metadata.Description = value;
|
||||
break;
|
||||
|
||||
case "argument1":
|
||||
metadata.Arguments[0] = ParseArgument(value);
|
||||
break;
|
||||
case "argument2":
|
||||
metadata.Arguments[1] = ParseArgument(value);
|
||||
break;
|
||||
case "argument3":
|
||||
metadata.Arguments[2] = ParseArgument(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public static ScriptMetadata? FromPowershell(string psFile)
|
||||
{
|
||||
return FromHashComments(psFile, "ps1");
|
||||
}
|
||||
|
||||
public static ScriptMetadata? FromPython(string pyFile)
|
||||
{
|
||||
return FromHashComments(pyFile, "py");
|
||||
}
|
||||
|
||||
public static ScriptMetadata? FromBash(string bashFile)
|
||||
{
|
||||
return FromHashComments(bashFile, "bash");
|
||||
}
|
||||
|
||||
public ICommand ToCommand(Settings settings)
|
||||
{
|
||||
return new DoScriptCommand(this, settings);
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
internal sealed partial class DoScriptCommand : InvokableWithParams
|
||||
{
|
||||
private ScriptMetadata Metadata { get; }
|
||||
|
||||
private Settings Settings { get; }
|
||||
|
||||
internal static readonly char[] Separator = new[] { '\n', '\r' };
|
||||
internal static readonly char[] SeparatorArray = new[] { '\n', '\r' };
|
||||
|
||||
internal DoScriptCommand(ScriptMetadata metadata, Settings settings)
|
||||
{
|
||||
Metadata = metadata;
|
||||
Settings = settings;
|
||||
Name = "Run script";
|
||||
BuildParams();
|
||||
}
|
||||
|
||||
public override ICommandResult InvokeWithArgs(object sender, ICommandArgument[] args)
|
||||
{
|
||||
// Determine which exe to use to run this command
|
||||
var exePath = string.Empty;
|
||||
var exeArgs = string.Empty;
|
||||
if (Metadata.Language.Equals("ps1", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
exePath = "pwsh.exe";
|
||||
exeArgs = $"-noprofile -nologo -File \"{Metadata.ScriptFilePath}\"";
|
||||
}
|
||||
else if (Metadata.Language.Equals("py", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
exePath = "python.exe";
|
||||
exeArgs = $"\"{Metadata.ScriptFilePath}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
var pathFromSettings = Settings.BashPath;
|
||||
|
||||
// split it
|
||||
exePath = pathFromSettings.Split(' ').FirstOrDefault() ?? "bash";
|
||||
exeArgs = pathFromSettings.Substring(exePath.Length).Trim();
|
||||
exeArgs += $"-c \"{Metadata.ScriptFilePath}\"";
|
||||
}
|
||||
|
||||
// If we have arguments, append them
|
||||
if (args != null && args.Length > 0 && Metadata.Arguments != null)
|
||||
{
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (arg != null && arg.Value is string s && !string.IsNullOrEmpty(s))
|
||||
{
|
||||
exeArgs += $" \"{s}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run the script, in the directory that the script is in
|
||||
var scriptDirectory = Path.GetDirectoryName(Metadata.ScriptFilePath);
|
||||
|
||||
switch (Metadata.Mode)
|
||||
{
|
||||
case ScriptMode.FullOutput:
|
||||
// In `fullOutput` the entire output is presented on a separate view, similar to a terminal window. This is handy when your script generates output to consume.
|
||||
ShellHelpers.OpenInShell(
|
||||
exePath,
|
||||
exeArgs,
|
||||
scriptDirectory,
|
||||
ShellHelpers.ShellRunAsType.None,
|
||||
runWithHiddenWindow: false);
|
||||
return CommandResult.Dismiss();
|
||||
case ScriptMode.Compact:
|
||||
{
|
||||
// In `compact` mode the last line of the standard output is shown in the toast
|
||||
|
||||
// Start the process and capture the output
|
||||
var process = new System.Diagnostics.Process
|
||||
{
|
||||
StartInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = exePath,
|
||||
Arguments = exeArgs,
|
||||
WorkingDirectory = scriptDirectory,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
},
|
||||
};
|
||||
process.Start();
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
|
||||
// Show the last line of output in a toast
|
||||
if (!string.IsNullOrEmpty(output))
|
||||
{
|
||||
var lastLine = output.Split(Separator, StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
|
||||
var toast = new ToastStatusMessage(new StatusMessage() { Message = lastLine ?? "Script executed successfully." });
|
||||
toast.Show();
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
case ScriptMode.Silent:
|
||||
{
|
||||
// In `silent` mode the last line (if exists) will be shown in overlaying HUD toast after Raycast window is closed
|
||||
|
||||
// Start the process and capture the output
|
||||
var process = new System.Diagnostics.Process
|
||||
{
|
||||
StartInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = exePath,
|
||||
Arguments = exeArgs,
|
||||
WorkingDirectory = scriptDirectory,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
},
|
||||
};
|
||||
process.Start();
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
string? lastLine = null;
|
||||
|
||||
// Show the last line of output in a toast
|
||||
if (!string.IsNullOrEmpty(output))
|
||||
{
|
||||
lastLine = output.Split(Separator, StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
|
||||
}
|
||||
|
||||
return CommandResult.ShowToast(lastLine ?? "Script executed successfully.");
|
||||
}
|
||||
|
||||
case ScriptMode.Inline:
|
||||
{
|
||||
// In `inline` mode, the first line of output will be directly shown in the command item and automatically refresh according to the specified `refreshTime`. Tip: Set your dashboard items as favorites via the action menu in Raycast.
|
||||
// TODO! **NOTE:** `refreshTime` parameter is required for `inline` mode. When not specified, `compact` mode will be used instead.
|
||||
|
||||
// In `silent` mode the last line (if exists) will be shown in overlaying HUD toast after Raycast window is closed
|
||||
|
||||
// Start the process and capture the output
|
||||
var process = new System.Diagnostics.Process
|
||||
{
|
||||
StartInfo = new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = exePath,
|
||||
Arguments = exeArgs,
|
||||
WorkingDirectory = scriptDirectory,
|
||||
RedirectStandardOutput = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
},
|
||||
};
|
||||
process.Start();
|
||||
process.WaitForExit();
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
string? lastLine = null;
|
||||
|
||||
// Show the last line of output in a toast
|
||||
if (!string.IsNullOrEmpty(output))
|
||||
{
|
||||
lastLine = output.Split(SeparatorArray, StringSplitOptions.RemoveEmptyEntries).LastOrDefault();
|
||||
}
|
||||
|
||||
// TODO!
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
|
||||
private void BuildParams()
|
||||
{
|
||||
if (Metadata.Arguments == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parameters = new List<CommandParameter>();
|
||||
foreach (var arg in Metadata.Arguments)
|
||||
{
|
||||
if (arg == null ||
|
||||
string.IsNullOrEmpty(arg.Placeholder) ||
|
||||
arg.Type != "text")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var param = new CommandParameter(arg.Placeholder, !arg.Optional);
|
||||
parameters.Add(param);
|
||||
}
|
||||
|
||||
this.Parameters = parameters.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
internal sealed partial class MarkdownPage : ContentPage
|
||||
{
|
||||
private readonly string _text = string.Empty;
|
||||
|
||||
public MarkdownPage(string text)
|
||||
{
|
||||
_text = text;
|
||||
Name = "Open";
|
||||
}
|
||||
|
||||
public override IContent[] GetContent() => [new MarkdownContent(_text)];
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
internal enum ScriptMode
|
||||
{
|
||||
FullOutput,
|
||||
Compact,
|
||||
Silent,
|
||||
Inline,
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "meh")]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(bool))]
|
||||
[JsonSerializable(typeof(List<string>), TypeInfoPropertyName = "StringList")]
|
||||
[JsonSerializable(typeof(ScriptArgument), TypeInfoPropertyName = "ScriptArgument")]
|
||||
[JsonSerializable(typeof(ScriptMetadata), TypeInfoPropertyName = "ScriptMetadata")]
|
||||
[JsonSerializable(typeof(DropdownItem), TypeInfoPropertyName = "DropdownItem")]
|
||||
[JsonSourceGenerationOptions(UseStringEnumConverter = true, WriteIndented = true, IncludeFields = true, PropertyNameCaseInsensitive = true, AllowTrailingCommas = true)]
|
||||
internal sealed partial class JsonSerializationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -169,6 +169,46 @@ internal sealed partial class SampleListPage : ListPage
|
||||
{
|
||||
Title = "Get the name of the Foreground window",
|
||||
},
|
||||
new ListItem(new CommandWithParams() { Name = "Invoke with params" })
|
||||
{
|
||||
Title = "Do a thing with a string",
|
||||
Subtitle = "This command requires more input",
|
||||
Icon = new IconInfo("\uE961"),
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
internal sealed partial class CommandWithParams : Command, IInvokableCommandWithParameters
|
||||
{
|
||||
public ICommandParameter[] Parameters => [new TextParam("Test")];
|
||||
|
||||
public ICommandResult Invoke(object sender) => throw new System.NotImplementedException();
|
||||
|
||||
public ICommandResult InvokeWithArgs(object sender, ICommandArgument[] args)
|
||||
{
|
||||
if (args.Length > 0)
|
||||
{
|
||||
var arg = args[0];
|
||||
var msg = $"Arg {arg.Name} = {arg.Value}";
|
||||
var toast = new ToastStatusMessage(new StatusMessage() { Message = msg, State = MessageState.Success });
|
||||
toast.Show();
|
||||
}
|
||||
else
|
||||
{
|
||||
var toast = new ToastStatusMessage(new StatusMessage() { Message = "didn't work homes", State = MessageState.Error });
|
||||
toast.Show();
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed partial class TextParam(string name, bool required = true) : ICommandParameter
|
||||
{
|
||||
public string Name => name;
|
||||
|
||||
public bool Required => required;
|
||||
|
||||
public ParameterType Type => ParameterType.Text;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +235,42 @@ namespace Microsoft.CommandPalette.Extensions
|
||||
Windows.Foundation.IAsyncAction LogMessage(ILogMessage message);
|
||||
};
|
||||
|
||||
enum ParameterType
|
||||
{
|
||||
Text,
|
||||
// File,
|
||||
// Files,
|
||||
Enum,
|
||||
Custom
|
||||
};
|
||||
|
||||
// interface IArgumentEnumValue requires INotifyPropChanged
|
||||
// {
|
||||
// String Name { get; };
|
||||
// IIconInfo Icon { get; };
|
||||
// }
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface ICommandArgument requires INotifyPropChanged
|
||||
{
|
||||
ParameterType Type { get; };
|
||||
String Name { get; };
|
||||
Boolean Required{ get; };
|
||||
|
||||
Object Value { get; set; };
|
||||
String DisplayName { get; };
|
||||
IIconInfo Icon { get; };
|
||||
|
||||
void ShowPicker(UInt64 hostHwnd);
|
||||
// todo
|
||||
// IArgumentEnumValue[] GetValues();
|
||||
};
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IInvokableCommandWithParameters requires ICommand {
|
||||
ICommandArgument[] Parameters { get; };
|
||||
ICommandResult InvokeWithArgs(Object sender, ICommandArgument[] args);
|
||||
};
|
||||
|
||||
[contract(Microsoft.CommandPalette.Extensions.ExtensionsContract, 1)]
|
||||
interface IPage requires ICommand {
|
||||
String Title { get; };
|
||||
|
||||