Compare commits

..

1 Commits

Author SHA1 Message Date
Clint Rutkas
8038df1b62 Update PowerToys download links to version 0.96.1 2025-11-28 23:29:59 -08:00
59 changed files with 141 additions and 2434 deletions

View File

@@ -56,7 +56,6 @@ ANull
AOC
aocfnapldcnfbofgmbbllojgocaelgdd
AOklab
aot
APARTMENTTHREADED
APeriod
apicontract
@@ -1058,7 +1057,6 @@ msrc
msstore
msvcp
MT
mstsc
MTND
MULTIPLEUSE
multizone
@@ -1208,11 +1206,8 @@ OOBEUI
openas
opencode
OPENFILENAME
openrdp
opensource
openxmlformats
ollama
onnx
OPTIMIZEFORINVOKE
ORPHANEDDIALOGTITLE
ORSCANS
@@ -1425,8 +1420,6 @@ RAWPATH
rbhid
rclsid
RCZOOMIT
remotedesktop
rdp
RDW
READMODE
READOBJECTS

View File

@@ -21,6 +21,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v6
uses: actions/checkout@v4
- name: 'Dependency Review'
uses: actions/dependency-review-action@v4

View File

@@ -27,7 +27,7 @@ jobs:
issue: ${{ fromJson(github.event.inputs.issue_numbers) }}
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v3
- name: Run GenAI Issue Deduplicator
uses: pelikhan/action-genai-issue-dedup@v0

View File

@@ -243,10 +243,6 @@ _If you want to find diagnostic data events in the source code, these two links
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdNotFound_EnableCmdNotFound</td>
<td>Triggered when Command Not Found is enabled or disabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdNotFoundInstallEvent</td>
<td>Triggered when a Command Not Found is installed.</td>
@@ -261,62 +257,6 @@ _If you want to find diagnostic data events in the source code, these two links
</tr>
</table>
### Command Palette
<table style="width:100%">
<tr>
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_BeginInvoke</td>
<td>Triggered when the Command Palette is launched by the user.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_ColdLaunch</td>
<td>Occurs when Command Palette starts for the first time (cold start).</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_OpenPage</td>
<td>Triggered when a page is opened within the Command Palette, tracking navigation depth.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_OpenUri</td>
<td>Occurs when a URI is opened through the Command Palette, including whether it's a web URL.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_ReactivateInstance</td>
<td>Triggered when an existing Command Palette instance is reactivated.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_RunCommand</td>
<td>Logs when a command is executed through the Command Palette, including admin elevation status.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPal_RunQuery</td>
<td>Triggered when a search query is performed, including result count and duration.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalDismissedOnEsc</td>
<td>Occurs when the Command Palette is dismissed by pressing the Escape key.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalDismissedOnLostFocus</td>
<td>Triggered when the Command Palette is dismissed due to losing focus.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalHotkeySummoned</td>
<td>Logs when the Command Palette is summoned via hotkey, distinguishing between global and context-specific hotkeys.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalInvokeResult</td>
<td>Records the result type of a Command Palette invocation.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.CmdPalProcessStarted</td>
<td>Triggered when the Command Palette process is started.</td>
</tr>
</table>
### Crop And Lock
<table style="width:100%">
<tr>
@@ -795,10 +735,6 @@ _If you want to find diagnostic data events in the source code, these two links
<th>Event Name</th>
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.NewPlus_ChangedTemplateLocation</td>
<td>Triggered when the template folder location is changed.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.NewPlus_EventCopyTemplate</td>
<td>Triggered when an item from New+ is created (copied to the current directory).</td>
@@ -807,10 +743,6 @@ _If you want to find diagnostic data events in the source code, these two links
<td>Microsoft.PowerToys.NewPlus_EventCopyTemplateResult</td>
<td>Logs the success of item creation (copying).</td>
</tr>
<tr>
<td>Microsoft.PowerToys.NewPlus_EventOpenTemplates</td>
<td>Triggered when the templates folder is opened.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.NewPlus_EventShowTemplateItems</td>
<td>Triggered when the New+ context menu flyout is displayed.</td>
@@ -996,8 +928,12 @@ _If you want to find diagnostic data events in the source code, these two links
<th>Description</th>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutGuide_GuideSession</td>
<td>Logs a Shortcut Guide session including duration and how it was closed.</td>
<td>Microsoft.PowerToys.ShortcutGuide_EnableGuide</td>
<td>Triggered when Shortcut Guide is enabled.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutGuide_HideGuide</td>
<td>Occurs when Shortcut Guide is hidden from view.</td>
</tr>
<tr>
<td>Microsoft.PowerToys.ShortcutGuide_Settings</td>

View File

@@ -69,7 +69,7 @@
This is present due to a bug in CsWinRT where WPF projects cause the analyzer to fail.
-->
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4948" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.250907003" />
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="1.8.37" />
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="1.8.250907003" />

View File

@@ -834,10 +834,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LanguageModelProvider", "sr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.UI.ViewModels.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.UI.ViewModels.UnitTests\Microsoft.CmdPal.UI.ViewModels.UnitTests.csproj", "{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.RemoteDesktop", "src\modules\cmdpal\ext\Microsoft.CmdPal.Ext.RemoteDesktop\Microsoft.CmdPal.Ext.RemoteDesktop.csproj", "{2B3FB837-23DE-629F-82C6-42304E7083C9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests\Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests.csproj", "{DB34808A-FF91-D06E-A426-AFB5A8BD583B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
@@ -3040,22 +3036,6 @@ Global
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|ARM64.Build.0 = Release|ARM64
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|x64.ActiveCfg = Release|x64
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C}.Release|x64.Build.0 = Release|x64
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|ARM64.ActiveCfg = Debug|ARM64
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|ARM64.Build.0 = Debug|ARM64
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|x64.ActiveCfg = Debug|x64
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Debug|x64.Build.0 = Debug|x64
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|ARM64.ActiveCfg = Release|ARM64
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|ARM64.Build.0 = Release|ARM64
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|x64.ActiveCfg = Release|x64
{2B3FB837-23DE-629F-82C6-42304E7083C9}.Release|x64.Build.0 = Release|x64
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|ARM64.ActiveCfg = Debug|ARM64
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|ARM64.Build.0 = Debug|ARM64
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|x64.ActiveCfg = Debug|x64
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Debug|x64.Build.0 = Debug|x64
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|ARM64.ActiveCfg = Release|ARM64
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|ARM64.Build.0 = Release|ARM64
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|x64.ActiveCfg = Release|x64
{DB34808A-FF91-D06E-A426-AFB5A8BD583B}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -3387,8 +3367,6 @@ Global
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{45354F4F-1414-45CE-B600-51CD1209FD19} = {1AFB6476-670D-4E80-A464-657E01DFF482}
{A66E9270-5D93-EC9C-F06E-CE7295BB9A6C} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
{2B3FB837-23DE-629F-82C6-42304E7083C9} = {ECB8E0D1-7603-4E5C-AB10-D1E545E6F8E2}
{DB34808A-FF91-D06E-A426-AFB5A8BD583B} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}

View File

@@ -31,11 +31,6 @@ namespace ManagedCommon
/// </summary>
public static string CurrentVersionLogDirectoryPath { get; private set; }
/// <summary>
/// Gets the path to the current log file.
/// </summary>
public static string CurrentLogFile { get; private set; }
/// <summary>
/// Gets the path to the log directory for the app.
/// </summary>
@@ -60,9 +55,7 @@ namespace ManagedCommon
AppLogDirectoryPath = basePath;
CurrentVersionLogDirectoryPath = versionedPath;
var logFile = "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log";
var logFilePath = Path.Combine(versionedPath, logFile);
CurrentLogFile = logFilePath;
var logFilePath = Path.Combine(versionedPath, "Log_" + DateTime.Now.ToString(@"yyyy-MM-dd", CultureInfo.InvariantCulture) + ".log");
Trace.Listeners.Add(new TextWriterTraceListener(logFilePath));

View File

@@ -11,15 +11,15 @@ public class GalleryGridPropertiesViewModel : IGridPropertiesViewModel
{
private readonly ExtensionObject<IGalleryGridLayout> _model;
public bool ShowTitle { get; private set; }
public bool ShowSubtitle { get; private set; }
public GalleryGridPropertiesViewModel(IGalleryGridLayout galleryGridLayout)
{
_model = new(galleryGridLayout);
}
public bool ShowTitle { get; set; }
public bool ShowSubtitle { get; set; }
public void InitializeProperties()
{
var model = _model.Unsafe;

View File

@@ -6,9 +6,5 @@ namespace Microsoft.CmdPal.Core.ViewModels;
public interface IGridPropertiesViewModel
{
bool ShowTitle { get; }
bool ShowSubtitle { get; }
void InitializeProperties();
}

View File

@@ -10,9 +10,10 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Core.ViewModels;
public partial class ListItemViewModel : CommandItemViewModel
public partial class ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
: CommandItemViewModel(new(model), context)
{
public new ExtensionObject<IListItem> Model { get; }
public new ExtensionObject<IListItem> Model { get; } = new(model);
public List<TagViewModel>? Tags { get; set; }
@@ -31,40 +32,6 @@ public partial class ListItemViewModel : CommandItemViewModel
public string AccessibleName { get; private set; } = string.Empty;
public bool ShowTitle { get; private set; }
public bool ShowSubtitle { get; private set; }
public bool LayoutShowsTitle
{
get;
set
{
if (SetProperty(ref field, value))
{
UpdateShowsTitle();
}
}
}
public bool LayoutShowsSubtitle
{
get;
set
{
if (SetProperty(ref field, value))
{
UpdateShowsSubtitle();
}
}
}
public ListItemViewModel(IListItem model, WeakReference<IPageContext> context)
: base(new(model), context)
{
Model = new ExtensionObject<IListItem>(model);
}
public override void InitializeProperties()
{
if (IsInitialized)
@@ -126,18 +93,16 @@ public partial class ListItemViewModel : CommandItemViewModel
switch (propertyName)
{
case nameof(model.Tags):
case nameof(Tags):
UpdateTags(model.Tags);
break;
case nameof(model.TextToSuggest):
TextToSuggest = model.TextToSuggest ?? string.Empty;
UpdateProperty(nameof(TextToSuggest));
case nameof(TextToSuggest):
this.TextToSuggest = model.TextToSuggest ?? string.Empty;
break;
case nameof(model.Section):
Section = model.Section ?? string.Empty;
UpdateProperty(nameof(Section));
case nameof(Section):
this.Section = model.Section ?? string.Empty;
break;
case nameof(model.Details):
case nameof(Details):
var extensionDetails = model.Details;
Details = extensionDetails is not null ? new(extensionDetails, PageContext) : null;
Details?.InitializeProperties();
@@ -145,24 +110,16 @@ public partial class ListItemViewModel : CommandItemViewModel
UpdateProperty(nameof(HasDetails));
UpdateShowDetailsCommand();
break;
case nameof(model.MoreCommands):
UpdateProperty(nameof(MoreCommands));
case nameof(MoreCommands):
AddShowDetailsCommands();
break;
case nameof(model.Title):
UpdateProperty(nameof(Title));
UpdateShowsTitle();
case nameof(Title):
case nameof(Subtitle):
UpdateAccessibleName();
break;
case nameof(model.Subtitle):
UpdateProperty(nameof(Subtitle));
UpdateShowsSubtitle();
UpdateAccessibleName();
break;
default:
UpdateProperty(propertyName);
break;
}
UpdateProperty(propertyName);
}
// TODO: Do we want filters to match descriptions and other properties? Tags, etc... Yes?
@@ -249,32 +206,11 @@ public partial class ListItemViewModel : CommandItemViewModel
// many COM exception issues.
Tags = [.. newTags];
// We're already in UI thread, so just raise the events
OnPropertyChanged(nameof(Tags));
OnPropertyChanged(nameof(HasTags));
UpdateProperty(nameof(Tags));
UpdateProperty(nameof(HasTags));
});
}
private void UpdateShowsTitle()
{
var oldShowTitle = ShowTitle;
ShowTitle = LayoutShowsTitle;
if (oldShowTitle != ShowTitle)
{
UpdateProperty(nameof(ShowTitle));
}
}
private void UpdateShowsSubtitle()
{
var oldShowSubtitle = ShowSubtitle;
ShowSubtitle = LayoutShowsSubtitle && !string.IsNullOrWhiteSpace(Subtitle);
if (oldShowSubtitle != ShowSubtitle)
{
UpdateProperty(nameof(ShowSubtitle));
}
}
protected override void UnsafeCleanup()
{
base.UnsafeCleanup();

View File

@@ -24,7 +24,8 @@ public partial class ListViewModel : PageViewModel, IDisposable
// Observable from MVVM Toolkit will auto create public properties that use INotifyPropertyChange change
// https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/observablegroupedcollections for grouping support
public ObservableCollection<ListItemViewModel> FilteredItems { get; } = [];
[ObservableProperty]
public partial ObservableCollection<ListItemViewModel> FilteredItems { get; set; } = [];
public FiltersViewModel? Filters { get; set; }
@@ -223,8 +224,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
// TODO we can probably further optimize this by also keeping a
// HashSet of every ExtensionObject we currently have, and only
// building new viewmodels for the ones we haven't already built.
var showsTitle = GridProperties?.ShowTitle ?? true;
var showsSubtitle = GridProperties?.ShowSubtitle ?? true;
foreach (var item in newItems)
{
// Check for cancellation during item processing
@@ -238,8 +237,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
// If an item fails to load, silently ignore it.
if (viewModel.SafeFastInit())
{
viewModel.LayoutShowsTitle = showsTitle;
viewModel.LayoutShowsSubtitle = showsSubtitle;
newViewModels.Add(viewModel);
}
}
@@ -586,7 +583,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
GridProperties?.InitializeProperties();
UpdateProperty(nameof(GridProperties));
ApplyLayoutToItems();
ShowDetails = model.ShowDetails;
UpdateProperty(nameof(ShowDetails));
@@ -612,15 +608,22 @@ public partial class ListViewModel : PageViewModel, IDisposable
model.ItemsChanged += Model_ItemsChanged;
}
private static IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
private IGridPropertiesViewModel? LoadGridPropertiesViewModel(IGridProperties? gridProperties)
{
return gridProperties switch
if (gridProperties is IMediumGridLayout mediumGridLayout)
{
IMediumGridLayout mediumGridLayout => new MediumGridPropertiesViewModel(mediumGridLayout),
IGalleryGridLayout galleryGridLayout => new GalleryGridPropertiesViewModel(galleryGridLayout),
ISmallGridLayout smallGridLayout => new SmallGridPropertiesViewModel(smallGridLayout),
_ => null,
};
return new MediumGridPropertiesViewModel(mediumGridLayout);
}
else if (gridProperties is IGalleryGridLayout galleryGridLayout)
{
return new GalleryGridPropertiesViewModel(galleryGridLayout);
}
else if (gridProperties is ISmallGridLayout smallGridLayout)
{
return new SmallGridPropertiesViewModel(smallGridLayout);
}
return null;
}
public void LoadMoreIfNeeded()
@@ -682,7 +685,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
GridProperties = LoadGridPropertiesViewModel(model.GridProperties);
GridProperties?.InitializeProperties();
UpdateProperty(nameof(IsGridView));
ApplyLayoutToItems();
break;
case nameof(ShowDetails):
ShowDetails = model.ShowDetails;
@@ -728,21 +730,6 @@ public partial class ListViewModel : PageViewModel, IDisposable
});
}
private void ApplyLayoutToItems()
{
lock (_listLock)
{
var showsTitle = GridProperties?.ShowTitle ?? true;
var showsSubtitle = GridProperties?.ShowSubtitle ?? true;
foreach (var item in Items)
{
item.LayoutShowsTitle = showsTitle;
item.LayoutShowsSubtitle = showsSubtitle;
}
}
}
public void Dispose()
{
GC.SuppressFinalize(this);

View File

@@ -11,15 +11,13 @@ public class MediumGridPropertiesViewModel : IGridPropertiesViewModel
{
private readonly ExtensionObject<IMediumGridLayout> _model;
public bool ShowTitle { get; private set; }
public bool ShowSubtitle => false;
public MediumGridPropertiesViewModel(IMediumGridLayout mediumGridLayout)
{
_model = new(mediumGridLayout);
}
public bool ShowTitle { get; set; }
public void InitializeProperties()
{
var model = _model.Unsafe;

View File

@@ -11,10 +11,6 @@ public class SmallGridPropertiesViewModel : IGridPropertiesViewModel
{
private readonly ExtensionObject<ISmallGridLayout> _model;
public bool ShowTitle => false;
public bool ShowSubtitle => false;
public SmallGridPropertiesViewModel(ISmallGridLayout smallGridLayout)
{
_model = new(smallGridLayout);

View File

@@ -36,7 +36,6 @@ public partial class MainListPage : DynamicListPage,
"com.microsoft.cmdpal.builtin.websearch",
"com.microsoft.cmdpal.builtin.windowssettings",
"com.microsoft.cmdpal.builtin.datetime",
"com.microsoft.cmdpal.builtin.remotedesktop",
];
private readonly IServiceProvider _serviceProvider;

View File

@@ -13,7 +13,6 @@ using Microsoft.CmdPal.Ext.Calc;
using Microsoft.CmdPal.Ext.ClipboardHistory;
using Microsoft.CmdPal.Ext.Indexer;
using Microsoft.CmdPal.Ext.Registry;
using Microsoft.CmdPal.Ext.RemoteDesktop;
using Microsoft.CmdPal.Ext.Shell;
using Microsoft.CmdPal.Ext.System;
using Microsoft.CmdPal.Ext.TimeDate;
@@ -152,7 +151,6 @@ public partial class App : Application
services.AddSingleton<ICommandProvider, BuiltInsCommandProvider>();
services.AddSingleton<ICommandProvider, TimeDateCommandsProvider>();
services.AddSingleton<ICommandProvider, SystemCommandExtensionProvider>();
services.AddSingleton<ICommandProvider, RemoteDesktopCommandProvider>();
// Models
services.AddSingleton<TopLevelCommandManager>();

View File

@@ -1,257 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
x:Class="Microsoft.CmdPal.UI.Controls.DevRibbon"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
mc:Ignorable="d">
<UserControl.Resources>
<DataTemplate x:Key="LogEntryTemplate" x:DataType="viewModels:LogEntryViewModel">
<controls:SettingsExpander Description="{x:Bind Description}" Header="{x:Bind Header}">
<controls:SettingsExpander.HeaderIcon>
<FontIcon Glyph="{x:Bind SeverityGlyph}" />
</controls:SettingsExpander.HeaderIcon>
<controls:SettingsExpander.Items>
<controls:SettingsCard HorizontalContentAlignment="Stretch" ContentAlignment="Vertical">
<ScrollViewer
MaxWidth="1160"
HorizontalScrollMode="Auto"
VerticalScrollMode="Auto">
<TextBlock
FontFamily="Consolas"
FontSize="11"
Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}"
IsTextSelectionEnabled="True"
Text="{x:Bind Details}"
TextWrapping="NoWrap" />
</ScrollViewer>
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</DataTemplate>
<converters:BoolToVisibilityConverter
x:Key="InvertedBoolToVisibilityConverter"
FalseValue="Visible"
TrueValue="Collapsed" />
</UserControl.Resources>
<Grid>
<Border
x:Name="RootBorder"
Height="26"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Background="{ThemeResource SettingsCardBackground}"
BorderBrush="{ThemeResource SurfaceStrokeColorFlyoutBrush}"
BorderThickness="1,0,1,1"
CornerRadius="0,0,8,8"
Opacity="0.3">
<Button
Padding="0"
CornerRadius="0,0,8,8"
FontSize="11"
PointerEntered="DevRibbonButton_PointerEntered"
PointerExited="DevRibbonButton_PointerExited">
<StackPanel Orientation="Horizontal">
<StackPanel
Padding="8,4"
VerticalAlignment="Center"
Background="DarkOrange"
Orientation="Horizontal"
Visibility="{x:Bind VisibleIfGreaterThanZero(ViewModel.WarningCount), Mode=OneWay}">
<FontIcon
Margin="0,0,8,0"
VerticalAlignment="Center"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Glyph="&#xE7BA;" />
<TextBlock VerticalAlignment="Center">
<Run Text="{x:Bind ViewModel.WarningCount, Mode=OneWay}" />
</TextBlock>
</StackPanel>
<StackPanel
Padding="8,4"
VerticalAlignment="Center"
Background="Maroon"
Orientation="Horizontal"
Visibility="{x:Bind VisibleIfGreaterThanZero(ViewModel.ErrorCount), Mode=OneWay}">
<FontIcon
Margin="0,0,8,0"
VerticalAlignment="Center"
FontFamily="{ThemeResource SymbolThemeFontFamily}"
FontSize="12"
Glyph="&#xEA39;" />
<TextBlock VerticalAlignment="Center">
<Run Text="{x:Bind ViewModel.ErrorCount, Mode=OneWay}" />
</TextBlock>
</StackPanel>
<Border Padding="8,4">
<Border.Background>
<SolidColorBrush Color="{x:Bind ViewModel.TagColor}" />
</Border.Background>
<TextBlock Padding="4" VerticalAlignment="Center">
<Run Text="{x:Bind ViewModel.Tag}" />
</TextBlock>
</Border>
</StackPanel>
<Button.Flyout>
<Flyout
Placement="Bottom"
ShouldConstrainToRootBounds="False"
SystemBackdrop="{ThemeResource AcrylicBackgroundFillColorDefaultBackdrop}">
<Flyout.FlyoutPresenterStyle>
<Style BasedOn="{StaticResource DefaultFlyoutPresenterStyle}" TargetType="FlyoutPresenter">
<Setter Property="MinWidth" Value="600" />
<Setter Property="MaxWidth" Value="1200" />
<Setter Property="Padding" Value="0" />
</Style>
</Flyout.FlyoutPresenterStyle>
<Grid x:Name="FlyoutContent">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Padding="16" Spacing="8">
<!-- Logs section -->
<TextBlock
Margin="1,0,0,6"
Style="{ThemeResource SettingsSectionHeaderTextBlockStyle}"
Text="Logs" />
<ItemsControl ItemTemplate="{StaticResource LogEntryTemplate}" ItemsSource="{x:Bind ViewModel.LatestLogs, Mode=OneWay}" />
<StackPanel Orientation="Horizontal" Spacing="8">
<Button Command="{x:Bind ViewModel.OpenLogFileCommand}" Content="Open Log File" />
<Button Command="{x:Bind ViewModel.OpenLogFolderCommand}" Content="Open Log Folder" />
<Button Command="{x:Bind ViewModel.ResetErrorCountersCommand}" Content="Clear Counters" />
</StackPanel>
<!-- Build info section -->
<TextBlock Style="{ThemeResource SettingsSectionHeaderTextBlockStyle}" Text="Build Info" />
<Border
Padding="16"
Background="{ThemeResource SettingsCardBackground}"
BorderBrush="{ThemeResource SettingsCardBorderBrush}"
BorderThickness="1">
<Grid ColumnSpacing="8">
<Grid.Resources>
<Style
x:Key="KeyTextBlockStyle"
BasedOn="{StaticResource CaptionTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="IsTextSelectionEnabled" Value="True" />
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
</Style>
<Style
x:Key="ValueTextBlockStyle"
BasedOn="{StaticResource CaptionTextBlockStyle}"
TargetType="TextBlock">
<Setter Property="IsTextSelectionEnabled" Value="True" />
<Setter Property="TextAlignment" Value="Right" />
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Style="{StaticResource KeyTextBlockStyle}"
Text="Configuration:" />
<TextBlock
Grid.Row="0"
Grid.Column="1"
Style="{StaticResource ValueTextBlockStyle}"
Text="{x:Bind ViewModel.BuildConfiguration, Mode=OneWay}" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Style="{StaticResource KeyTextBlockStyle}"
Text="AOT:" />
<TextBlock
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource ValueTextBlockStyle}"
Text="{x:Bind ViewModel.IsAot, Mode=OneWay}" />
<TextBlock
Grid.Row="2"
Grid.Column="0"
Style="{StaticResource KeyTextBlockStyle}"
Text="Trimmed:" />
<TextBlock
Grid.Row="2"
Grid.Column="1"
Style="{StaticResource ValueTextBlockStyle}"
Text="{x:Bind ViewModel.IsPublishTrimmed, Mode=OneWay}" />
</Grid>
</Border>
</StackPanel>
<!-- Footer -->
<Border
Grid.Row="1"
Padding="16"
Background="{ThemeResource SettingsCardBackground}"
BorderBrush="{ThemeResource SettingsCardBorderBrush}"
BorderThickness="1"
CornerRadius="{ThemeResource ControlCornerRadius}"
Visibility="{x:Bind ViewModel.IsAotReleaseConfiguration, Mode=OneWay, Converter={StaticResource InvertedBoolToVisibilityConverter}}">
<TextBlock Text="Warning: Test in Release/AOT configuration to verify everything works." TextWrapping="Wrap" />
</Border>
</Grid>
</Flyout>
</Button.Flyout>
</Button>
</Border>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="PointerOver">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="RootBorder"
Storyboard.TargetProperty="Opacity"
To="1.0"
Duration="0:0:0.1" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="SeverityStates">
<VisualState x:Name="NoLog" />
<VisualState x:Name="WarningLog">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SeverityIcon" Storyboard.TargetProperty="Glyph">
<DiscreteObjectKeyFrame KeyTime="0" Value="&#xE7BA;" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="ErrorLog">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SeverityIcon" Storyboard.TargetProperty="Glyph">
<DiscreteObjectKeyFrame KeyTime="0" Value="&#xEA39;" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</UserControl>

View File

@@ -1,40 +0,0 @@
// 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.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Input;
namespace Microsoft.CmdPal.UI.Controls;
internal sealed partial class DevRibbon : UserControl
{
public ViewModels.DevRibbonViewModel ViewModel { get; }
public DevRibbon()
{
InitializeComponent();
ViewModel = new ViewModels.DevRibbonViewModel();
if (FlyoutContent != null)
{
FlyoutContent.DataContext = ViewModel;
}
}
private void DevRibbonButton_PointerEntered(object sender, PointerRoutedEventArgs e)
{
VisualStateManager.GoToState(this, "PointerOver", true);
}
private void DevRibbonButton_PointerExited(object sender, PointerRoutedEventArgs e)
{
VisualStateManager.GoToState(this, "Normal", true);
}
private Visibility VisibleIfGreaterThanZero(int value)
{
return value > 0 ? Visibility.Visible : Visibility.Collapsed;
}
}

View File

@@ -1,31 +0,0 @@
// 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;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Microsoft.CmdPal.UI;
internal sealed partial class GridItemContainerStyleSelector : StyleSelector
{
public IGridPropertiesViewModel? GridProperties { get; set; }
public Style? Small { get; set; }
public Style? Medium { get; set; }
public Style? Gallery { get; set; }
protected override Style? SelectStyleCore(object item, DependencyObject container)
{
return GridProperties switch
{
SmallGridPropertiesViewModel => Small,
MediumGridPropertiesViewModel => Medium,
GalleryGridPropertiesViewModel => Gallery,
_ => Medium,
};
}
}

View File

@@ -20,12 +20,21 @@ internal sealed partial class GridItemTemplateSelector : DataTemplateSelector
protected override DataTemplate? SelectTemplateCore(object item, DependencyObject dependencyObject)
{
return GridProperties switch
DataTemplate? dataTemplate = Medium;
if (GridProperties is SmallGridPropertiesViewModel)
{
SmallGridPropertiesViewModel => Small,
MediumGridPropertiesViewModel => Medium,
GalleryGridPropertiesViewModel => Gallery,
_ => Medium,
};
dataTemplate = Small;
}
else if (GridProperties is MediumGridPropertiesViewModel)
{
dataTemplate = Medium;
}
else if (GridProperties is GalleryGridPropertiesViewModel)
{
dataTemplate = Gallery;
}
return dataTemplate;
}
}

View File

@@ -5,151 +5,33 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cmdpalUI="using:Microsoft.CmdPal.UI"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
xmlns:coreViewModels="using:Microsoft.CmdPal.Core.ViewModels"
xmlns:cpcontrols="using:Microsoft.CmdPal.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:help="using:Microsoft.CmdPal.UI.Helpers"
xmlns:local="using:Microsoft.CmdPal.UI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:ui="using:CommunityToolkit.WinUI"
xmlns:viewModels="using:Microsoft.CmdPal.UI.ViewModels"
x:Name="PageRoot"
Background="Transparent"
DataContext="{x:Bind ViewModel, Mode=OneWay}"
mc:Ignorable="d">
<Page.Resources>
<!-- TODO: Figure out what we want to do here for filtering/grouping and where -->
<!-- https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.data.collectionviewsource -->
<!--<CollectionViewSource
x:Name="ItemsCVS"
IsSourceGrouped="True"
Source="{x:Bind ViewModel.Items, Mode=OneWay}" />-->
<!--
GridViewItemCornerRadius is the corner radius defined in GridView template; make
it bigger to match the radii of the gallery
-->
<CornerRadius x:Key="GalleryGridViewItemContainerCornerRadius">6</CornerRadius>
<CornerRadius x:Key="IconGridViewItemContainerCornerRadius">4</CornerRadius>
<CornerRadius x:Key="GalleryGridViewItemRadius">4</CornerRadius>
<CornerRadius x:Key="SmallGridViewItemCornerRadius">8</CornerRadius>
<CornerRadius x:Key="MediumGridViewItemCornerRadius">8</CornerRadius>
<Style x:Key="IconGridViewItemStyle" TargetType="GridViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewItem">
<ListViewItemPresenter
x:Name="Root"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
CheckBoxBorderBrush="{ThemeResource GridViewItemCheckBoxBorderBrush}"
CheckBoxBrush="{ThemeResource GridViewItemCheckBoxBrush}"
CheckBoxCornerRadius="{ThemeResource GridViewItemCheckBoxCornerRadius}"
CheckBoxDisabledBorderBrush="{ThemeResource GridViewItemCheckBoxDisabledBorderBrush}"
CheckBoxDisabledBrush="{ThemeResource GridViewItemCheckBoxDisabledBrush}"
CheckBoxPointerOverBorderBrush="{ThemeResource GridViewItemCheckBoxPointerOverBorderBrush}"
CheckBoxPointerOverBrush="{ThemeResource GridViewItemCheckBoxPointerOverBrush}"
CheckBoxPressedBorderBrush="{ThemeResource GridViewItemCheckBoxPressedBorderBrush}"
CheckBoxPressedBrush="{ThemeResource GridViewItemCheckBoxPressedBrush}"
CheckBoxSelectedBrush="{ThemeResource GridViewItemCheckBoxSelectedBrush}"
CheckBoxSelectedDisabledBrush="{ThemeResource GridViewItemCheckBoxSelectedDisabledBrush}"
CheckBoxSelectedPointerOverBrush="{ThemeResource GridViewItemCheckBoxSelectedPointerOverBrush}"
CheckBoxSelectedPressedBrush="{ThemeResource GridViewItemCheckBoxSelectedPressedBrush}"
CheckBrush="{ThemeResource GridViewItemCheckBrush}"
CheckDisabledBrush="{ThemeResource GridViewItemCheckDisabledBrush}"
CheckMode="{ThemeResource GridViewItemCheckMode}"
CheckPressedBrush="{ThemeResource GridViewItemCheckPressedBrush}"
ContentMargin="{TemplateBinding Padding}"
ContentTransitions="{TemplateBinding ContentTransitions}"
Control.IsTemplateFocusTarget="True"
CornerRadius="{StaticResource IconGridViewItemContainerCornerRadius}"
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
DragBackground="{ThemeResource GridViewItemDragBackground}"
DragForeground="{ThemeResource GridViewItemDragForeground}"
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
FocusBorderBrush="{ThemeResource GridViewItemFocusBorderBrush}"
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
FocusVisualPrimaryBrush="{TemplateBinding FocusVisualPrimaryBrush}"
FocusVisualPrimaryThickness="{TemplateBinding FocusVisualPrimaryThickness}"
FocusVisualSecondaryBrush="{TemplateBinding FocusVisualSecondaryBrush}"
FocusVisualSecondaryThickness="{TemplateBinding FocusVisualSecondaryThickness}"
PlaceholderBackground="{ThemeResource GridViewItemPlaceholderBackground}"
PointerOverBackground="{ThemeResource GridViewItemBackgroundPointerOver}"
PointerOverBorderBrush="{ThemeResource GridViewItemPointerOverBorderBrush}"
PointerOverForeground="{ThemeResource GridViewItemForegroundPointerOver}"
PressedBackground="{ThemeResource GridViewItemBackgroundPressed}"
ReorderHintOffset="{ThemeResource GridViewItemReorderHintThemeOffset}"
SelectedBackground="{ThemeResource GridViewItemBackgroundSelected}"
SelectedBorderBrush="{ThemeResource GridViewItemSelectedBorderBrush}"
SelectedBorderThickness="{ThemeResource GridViewItemSelectedBorderThickness}"
SelectedDisabledBackground="{ThemeResource GridViewItemBackgroundSelectedDisabled}"
SelectedDisabledBorderBrush="{ThemeResource GridViewItemSelectedDisabledBorderBrush}"
SelectedForeground="{ThemeResource GridViewItemForegroundSelected}"
SelectedInnerBorderBrush="{ThemeResource GridViewItemSelectedInnerBorderBrush}"
SelectedPointerOverBackground="{ThemeResource GridViewItemBackgroundSelectedPointerOver}"
SelectedPointerOverBorderBrush="{ThemeResource GridViewItemSelectedPointerOverBorderBrush}"
SelectedPressedBackground="{ThemeResource GridViewItemBackgroundSelectedPressed}"
SelectedPressedBorderBrush="{ThemeResource GridViewItemSelectedPressedBorderBrush}"
SelectionCheckMarkVisualEnabled="{ThemeResource GridViewItemSelectionCheckMarkVisualEnabled}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="GalleryGridViewItemStyle" TargetType="GridViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GridViewItem">
<ListViewItemPresenter
x:Name="Root"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
CheckBoxBorderBrush="{ThemeResource GridViewItemCheckBoxBorderBrush}"
CheckBoxBrush="{ThemeResource GridViewItemCheckBoxBrush}"
CheckBoxCornerRadius="{ThemeResource GridViewItemCheckBoxCornerRadius}"
CheckBoxDisabledBorderBrush="{ThemeResource GridViewItemCheckBoxDisabledBorderBrush}"
CheckBoxDisabledBrush="{ThemeResource GridViewItemCheckBoxDisabledBrush}"
CheckBoxPointerOverBorderBrush="{ThemeResource GridViewItemCheckBoxPointerOverBorderBrush}"
CheckBoxPointerOverBrush="{ThemeResource GridViewItemCheckBoxPointerOverBrush}"
CheckBoxPressedBorderBrush="{ThemeResource GridViewItemCheckBoxPressedBorderBrush}"
CheckBoxPressedBrush="{ThemeResource GridViewItemCheckBoxPressedBrush}"
CheckBoxSelectedBrush="{ThemeResource GridViewItemCheckBoxSelectedBrush}"
CheckBoxSelectedDisabledBrush="{ThemeResource GridViewItemCheckBoxSelectedDisabledBrush}"
CheckBoxSelectedPointerOverBrush="{ThemeResource GridViewItemCheckBoxSelectedPointerOverBrush}"
CheckBoxSelectedPressedBrush="{ThemeResource GridViewItemCheckBoxSelectedPressedBrush}"
CheckBrush="{ThemeResource GridViewItemCheckBrush}"
CheckDisabledBrush="{ThemeResource GridViewItemCheckDisabledBrush}"
CheckMode="{ThemeResource GridViewItemCheckMode}"
CheckPressedBrush="{ThemeResource GridViewItemCheckPressedBrush}"
ContentMargin="{TemplateBinding Padding}"
ContentTransitions="{TemplateBinding ContentTransitions}"
Control.IsTemplateFocusTarget="True"
CornerRadius="{StaticResource GalleryGridViewItemContainerCornerRadius}"
DisabledOpacity="{ThemeResource ListViewItemDisabledThemeOpacity}"
DragBackground="{ThemeResource GridViewItemDragBackground}"
DragForeground="{ThemeResource GridViewItemDragForeground}"
DragOpacity="{ThemeResource ListViewItemDragThemeOpacity}"
FocusBorderBrush="{ThemeResource GridViewItemFocusBorderBrush}"
FocusVisualMargin="{TemplateBinding FocusVisualMargin}"
FocusVisualPrimaryBrush="{TemplateBinding FocusVisualPrimaryBrush}"
FocusVisualPrimaryThickness="{TemplateBinding FocusVisualPrimaryThickness}"
FocusVisualSecondaryBrush="{TemplateBinding FocusVisualSecondaryBrush}"
FocusVisualSecondaryThickness="{TemplateBinding FocusVisualSecondaryThickness}"
PlaceholderBackground="{ThemeResource GridViewItemPlaceholderBackground}"
PointerOverBackground="{ThemeResource GridViewItemBackgroundPointerOver}"
PointerOverBorderBrush="{ThemeResource GridViewItemPointerOverBorderBrush}"
PointerOverForeground="{ThemeResource GridViewItemForegroundPointerOver}"
PressedBackground="{ThemeResource GridViewItemBackgroundPressed}"
ReorderHintOffset="{ThemeResource GridViewItemReorderHintThemeOffset}"
SelectedBackground="{ThemeResource GridViewItemBackgroundSelected}"
SelectedBorderBrush="{ThemeResource GridViewItemSelectedBorderBrush}"
SelectedBorderThickness="{ThemeResource GridViewItemSelectedBorderThickness}"
SelectedDisabledBackground="{ThemeResource GridViewItemBackgroundSelectedDisabled}"
SelectedDisabledBorderBrush="{ThemeResource GridViewItemSelectedDisabledBorderBrush}"
SelectedForeground="{ThemeResource GridViewItemForegroundSelected}"
SelectedInnerBorderBrush="{ThemeResource GridViewItemSelectedInnerBorderBrush}"
SelectedPointerOverBackground="{ThemeResource GridViewItemBackgroundSelectedPointerOver}"
SelectedPointerOverBorderBrush="{ThemeResource GridViewItemSelectedPointerOverBorderBrush}"
SelectedPressedBackground="{ThemeResource GridViewItemBackgroundSelectedPressed}"
SelectedPressedBorderBrush="{ThemeResource GridViewItemSelectedPressedBorderBrush}"
SelectionCheckMarkVisualEnabled="{ThemeResource GridViewItemSelectionCheckMarkVisualEnabled}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:StringVisibilityConverter
x:Key="StringVisibilityConverter"
EmptyValue="Collapsed"
NotEmptyValue="Visible" />
<DataTemplate x:Key="TagTemplate" x:DataType="coreViewModels:TagViewModel">
<cpcontrols:Tag
@@ -166,17 +48,10 @@
x:Key="GridItemTemplateSelector"
x:DataType="coreViewModels:ListItemViewModel"
Gallery="{StaticResource GalleryGridItemViewModelTemplate}"
GridProperties="{x:Bind ViewModel.GridProperties, Mode=OneWay}"
GridProperties="{x:Bind ViewModel.GridProperties}"
Medium="{StaticResource MediumGridItemViewModelTemplate}"
Small="{StaticResource SmallGridItemViewModelTemplate}" />
<cmdpalUI:GridItemContainerStyleSelector
x:Key="GridItemContainerStyleSelector"
Gallery="{StaticResource GalleryGridViewItemStyle}"
GridProperties="{x:Bind ViewModel.GridProperties, Mode=OneWay}"
Medium="{StaticResource IconGridViewItemStyle}"
Small="{StaticResource IconGridViewItemStyle}" />
<!-- https://learn.microsoft.com/windows/apps/design/controls/itemsview#specify-the-look-of-the-items -->
<DataTemplate x:Key="ListItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<Grid
@@ -219,7 +94,7 @@
Text="{x:Bind Subtitle, Mode=OneWay}"
TextTrimming="CharacterEllipsis"
TextWrapping="NoWrap"
Visibility="{x:Bind ShowSubtitle, Mode=OneWay}" />
Visibility="{x:Bind Subtitle, Mode=OneWay, Converter={StaticResource StringVisibilityConverter}}" />
</StackPanel>
<ItemsControl
@@ -249,11 +124,11 @@
Padding="8,16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
AutomationProperties.Name="{x:Bind Title}"
BorderThickness="0"
CornerRadius="{StaticResource SmallGridViewItemCornerRadius}"
CornerRadius="8"
Orientation="Vertical"
ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
ToolTipService.ToolTip="{x:Bind Title}">
<cpcontrols:IconBox
x:Name="GridIconBorder"
@@ -270,22 +145,23 @@
</DataTemplate>
<DataTemplate x:Key="MediumGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
<Grid
<StackPanel
Width="100"
Height="100"
Padding="8"
AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
CornerRadius="{StaticResource MediumGridViewItemCornerRadius}"
ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
Padding="8,16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind Title}"
BorderThickness="0"
CornerRadius="8"
Orientation="Vertical"
ToolTipService.ToolTip="{x:Bind Title}">
<cpcontrols:IconBox
x:Name="GridIconBorder"
Grid.Row="0"
Width="36"
Height="36"
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CharacterSpacing="12"
@@ -293,20 +169,21 @@
Foreground="{ThemeResource TextFillColorPrimary}"
SourceKey="{x:Bind Icon, Mode=OneWay}"
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
<TextBlock
x:Name="TitleTextBlock"
Grid.Row="1"
Height="32"
Margin="0,8,0,0"
MaxHeight="40"
Margin="0,8,0,4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CharacterSpacing="12"
FontSize="12"
Text="{x:Bind Title, Mode=OneWay}"
Text="{x:Bind Title}"
TextAlignment="Center"
TextTrimming="WordEllipsis"
TextWrapping="Wrap"
Visibility="{x:Bind LayoutShowsTitle, Mode=OneWay}" />
</Grid>
Visibility="{Binding ElementName=PageRoot, Path=DataContext.GridProperties.ShowTitle, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="GalleryGridItemViewModelTemplate" x:DataType="coreViewModels:ListItemViewModel">
@@ -316,11 +193,11 @@
Padding="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
AutomationProperties.Name="{x:Bind Title, Mode=OneWay}"
AutomationProperties.Name="{x:Bind Title}"
BorderThickness="0"
CornerRadius="{StaticResource GalleryGridViewItemRadius}"
CornerRadius="4"
Orientation="Vertical"
ToolTipService.ToolTip="{x:Bind Title, Mode=OneWay}">
ToolTipService.ToolTip="{x:Bind Title}">
<Grid
Width="160"
@@ -328,8 +205,12 @@
Margin="0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
CornerRadius="{StaticResource GalleryGridViewItemRadius}">
CornerRadius="4">
<Grid.RowDefinitions>
<RowDefinition />
</Grid.RowDefinitions>
<Viewbox
Grid.Row="1"
HorizontalAlignment="Center"
Stretch="UniformToFill"
StretchDirection="Both">
@@ -341,39 +222,35 @@
</Viewbox>
</Grid>
<StackPanel
Padding="4"
Orientation="Vertical"
Spacing="4"
Visibility="{x:Bind help:BindTransformers.VisibleWhenAny(ShowTitle, ShowSubtitle)}">
<StackPanel Padding="4" Orientation="Vertical">
<TextBlock
x:Name="TitleTextBlock"
MaxWidth="152"
MaxHeight="40"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
CharacterSpacing="12"
FontSize="14"
Foreground="{ThemeResource TextFillColorPrimary}"
Text="{x:Bind Title, Mode=OneWay}"
Text="{x:Bind Title}"
TextAlignment="Center"
TextTrimming="WordEllipsis"
TextWrapping="NoWrap"
Visibility="{x:Bind ShowTitle, Mode=OneWay}" />
TextWrapping="NoWrap" />
<TextBlock
x:Name="SubTitleTextBlock"
MaxWidth="152"
MaxHeight="40"
Margin="0,4,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
CharacterSpacing="11"
FontSize="11"
Foreground="{ThemeResource TextFillColorTertiary}"
Text="{x:Bind Subtitle, Mode=OneWay}"
Text="{x:Bind Subtitle}"
TextAlignment="Center"
TextTrimming="WordEllipsis"
TextWrapping="NoWrap"
Visibility="{x:Bind ShowSubtitle, Mode=OneWay}" />
TextWrapping="NoWrap" />
</StackPanel>
</StackPanel>
</DataTemplate>
@@ -418,7 +295,6 @@
IsDoubleTapEnabled="True"
IsItemClickEnabled="True"
ItemClick="Items_ItemClick"
ItemContainerStyleSelector="{StaticResource GridItemContainerStyleSelector}"
ItemTemplateSelector="{StaticResource GridItemTemplateSelector}"
ItemsSource="{x:Bind ViewModel.FilteredItems, Mode=OneWay}"
RightTapped="Items_RightTapped"
@@ -426,7 +302,6 @@
<GridView.ItemContainerTransitions>
<TransitionCollection />
</GridView.ItemContainerTransitions>
<GridView.ItemContainerStyle />
</GridView>
</controls:Case>
</controls:SwitchPresenter>

View File

@@ -15,7 +15,4 @@ internal static class BindTransformers
public static Visibility EmptyOrWhitespaceToCollapsed(string? input)
=> string.IsNullOrWhiteSpace(input) ? Visibility.Collapsed : Visibility.Visible;
public static Visibility VisibleWhenAny(bool value1, bool value2)
=> (value1 || value2) ? Visibility.Visible : Visibility.Collapsed;
}

View File

@@ -1,36 +0,0 @@
// 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.Reflection;
using System.Runtime.CompilerServices;
namespace Microsoft.CmdPal.UI.Helpers;
internal static class BuildInfo
{
#if DEBUG
public const string Configuration = "Debug";
#else
public const string Configuration = "Release";
#endif
// Runtime AOT detection
public static bool IsNativeAot => !RuntimeFeature.IsDynamicCodeSupported;
// From assembly metadata (build-time values)
public static bool PublishTrimmed => GetBoolMetadata("PublishTrimmed", false);
// From assembly metadata (build-time values)
public static bool PublishAot => GetBoolMetadata("PublishAot", false);
public static bool IsCiBuild => GetBoolMetadata("CIBuild", false);
private static string? GetMetadata(string key) =>
Assembly.GetExecutingAssembly()
.GetCustomAttributes<AssemblyMetadataAttribute>()
.FirstOrDefault(a => a.Key == key)?.Value;
private static bool GetBoolMetadata(string key, bool defaultValue) =>
bool.TryParse(GetMetadata(key), out var result) ? result : defaultValue;
}

View File

@@ -14,7 +14,5 @@
Activated="MainWindow_Activated"
Closed="MainWindow_Closed"
mc:Ignorable="d">
<Grid x:Name="RootElement">
<pages:ShellPage />
</Grid>
<pages:ShellPage x:Name="RootShellPage" />
</winuiex:WindowEx>

View File

@@ -11,7 +11,6 @@ using Microsoft.CmdPal.Core.Common.Helpers;
using Microsoft.CmdPal.Core.Common.Services;
using Microsoft.CmdPal.Core.ViewModels.Messages;
using Microsoft.CmdPal.Ext.ClipboardHistory.Messages;
using Microsoft.CmdPal.UI.Controls;
using Microsoft.CmdPal.UI.Events;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.CmdPal.UI.Messages;
@@ -114,7 +113,7 @@ public sealed partial class MainWindow : WindowEx,
ExtendsContentIntoTitleBar = true;
AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Collapsed;
SizeChanged += WindowSizeChanged;
RootElement.Loaded += RootElementLoaded;
RootShellPage.Loaded += RootShellPage_Loaded;
WM_TASKBAR_RESTART = PInvoke.RegisterWindowMessage("TaskbarCreated");
@@ -131,7 +130,7 @@ public sealed partial class MainWindow : WindowEx,
App.Current.Services.GetService<SettingsModel>()!.SettingsChanged += SettingsChangedHandler;
// Make sure that we update the acrylic theme when the OS theme changes
RootElement.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
RootShellPage.ActualThemeChanged += (s, e) => DispatcherQueue.TryEnqueue(UpdateAcrylic);
// Hardcoding event name to avoid bringing in the PowerToys.interop dependency. Event name must match CMDPAL_SHOW_EVENT from shared_constants.h
NativeEventWaiter.WaitForEventLoop("Local\\PowerToysCmdPal-ShowEvent-62336fcd-8611-4023-9b30-091a6af4cc5a", () =>
@@ -166,18 +165,11 @@ public sealed partial class MainWindow : WindowEx,
private void SettingsChangedHandler(SettingsModel sender, object? args) => HotReloadSettings();
private void RootElementLoaded(object sender, RoutedEventArgs e)
{
private void RootShellPage_Loaded(object sender, RoutedEventArgs e) =>
// Now that our content has loaded, we can update our draggable regions
UpdateRegionsForCustomTitleBar();
// Add dev ribbon if enabled
if (!BuildInfo.IsCiBuild)
{
RootElement.Children.Add(new DevRibbon { Margin = new Thickness(-1, -1, 120, -1) });
}
}
private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs args) => UpdateRegionsForCustomTitleBar();
private void PositionCentered()
@@ -666,28 +658,28 @@ public sealed partial class MainWindow : WindowEx,
private void UpdateRegionsForCustomTitleBar()
{
// Specify the interactive regions of the title bar.
var scaleAdjustment = RootElement.XamlRoot.RasterizationScale;
var scaleAdjustment = RootShellPage.XamlRoot.RasterizationScale;
// Get the rectangle around our XAML content. We're going to mark this
// rectangle as "Passthrough", so that the normal window operations
// (resizing, dragging) don't apply in this space.
var transform = RootElement.TransformToVisual(null);
var transform = RootShellPage.TransformToVisual(null);
// Reserve 16px of space at the top for dragging.
var topHeight = 16;
var bounds = transform.TransformBounds(new Rect(
0,
topHeight,
RootElement.ActualWidth,
RootElement.ActualHeight));
RootShellPage.ActualWidth,
RootShellPage.ActualHeight));
var contentRect = GetRect(bounds, scaleAdjustment);
var rectArray = new RectInt32[] { contentRect };
var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(this.AppWindow.Id);
nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rectArray);
// Add a drag-able region on top
var w = RootElement.ActualWidth;
_ = RootElement.ActualHeight;
var w = RootShellPage.ActualWidth;
_ = RootShellPage.ActualHeight;
var dragSides = new RectInt32[]
{
GetRect(new Rect(0, 0, w, topHeight), scaleAdjustment), // the top, {topHeight=16} tall

View File

@@ -15,7 +15,6 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
<LangVersion>preview</LangVersion>
<Version>$(CmdPalVersion)</Version>
@@ -26,10 +25,10 @@
</PropertyGroup>
<!-- For debugging purposes, uncomment this block to enable AOT builds -->
<!--<PropertyGroup>
<!-- <PropertyGroup>
<EnableCmdPalAOT>true</EnableCmdPalAOT>
<GeneratePackageLocally>true</GeneratePackageLocally>
</PropertyGroup>-->
<CIBuild>true</CIBuild>
</PropertyGroup> -->
<PropertyGroup Condition="'$(EnableCmdPalAOT)' == 'true'">
<SelfContained>true</SelfContained>
@@ -38,7 +37,7 @@
<PublishAot>true</PublishAot>
</PropertyGroup>
<PropertyGroup Condition="'$(CIBuild)' == 'true' or '$(GeneratePackageLocally)' == 'true'">
<PropertyGroup Condition="'$(CIBuild)'=='true'">
<GenerateAppxPackageOnBuild>true</GenerateAppxPackageOnBuild>
<AppxBundle>Never</AppxBundle>
<AppxPackageTestDir>$(OutputPath)\AppPackages\Microsoft.CmdPal.UI_$(Version)_Test\</AppxPackageTestDir>
@@ -67,7 +66,6 @@
<ItemGroup>
<None Remove="Controls\ActionBar.xaml" />
<None Remove="Controls\DevRibbon.xaml" />
<None Remove="Controls\KeyVisual\KeyCharPresenter.xaml" />
<None Remove="Controls\SearchBar.xaml" />
<None Remove="IsEnabledTextBlock.xaml" />
@@ -120,7 +118,6 @@
<ItemGroup>
<ProjectReference Include="..\..\..\common\ManagedTelemetry\Telemetry\ManagedTelemetry.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.ClipboardHistory\Microsoft.CmdPal.Ext.ClipboardHistory.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.RemoteDesktop\Microsoft.CmdPal.Ext.RemoteDesktop.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.System\Microsoft.CmdPal.Ext.System.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.WebSearch\Microsoft.CmdPal.Ext.WebSearch.csproj" />
<ProjectReference Include="..\ext\Microsoft.CmdPal.Ext.Indexer\Microsoft.CmdPal.Ext.Indexer.csproj" />
@@ -170,9 +167,6 @@
<Page Update="Controls\SearchBar.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Controls\DevRibbon.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
<Page Update="Styles\TextBox.xaml">
<XamlRuntime>$(DefaultXamlRuntime)</XamlRuntime>
</Page>
@@ -240,24 +234,4 @@
</ItemGroup>
<!-- </AdaptiveCardsWorkaround> -->
<!-- Metadata for build information -->
<ItemGroup>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>PublishTrimmed</_Parameter1>
<_Parameter2>$(PublishTrimmed)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>PublishAot</_Parameter1>
<_Parameter2>$(PublishAot)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>CIBuild</_Parameter1>
<_Parameter2>$(CIBuild)</_Parameter2>
</AssemblyAttribute>
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute">
<_Parameter1>CommandPaletteBranding</_Parameter1>
<_Parameter2>$(CommandPaletteBranding)</_Parameter2>
</AssemblyAttribute>
</ItemGroup>
</Project>

View File

@@ -1,190 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using ManagedCommon;
using Microsoft.CmdPal.UI.Helpers;
using Microsoft.UI;
using Windows.System;
using Windows.UI;
using DispatcherQueue = Microsoft.UI.Dispatching.DispatcherQueue;
namespace Microsoft.CmdPal.UI.ViewModels;
internal sealed partial class DevRibbonViewModel : ObservableObject
{
private const int MaxLogEntries = 2;
private const string Release = "Release";
private const string Debug = "Debug";
private static readonly Color ReleaseAotColor = ColorHelper.FromArgb(255, 124, 58, 237);
private static readonly Color ReleaseColor = ColorHelper.FromArgb(255, 51, 65, 85);
private static readonly Color DebugAotColor = ColorHelper.FromArgb(255, 99, 102, 241);
private static readonly Color DebugColor = ColorHelper.FromArgb(255, 107, 114, 128);
private readonly DispatcherQueue _dispatcherQueue;
public DevRibbonViewModel()
{
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
Trace.Listeners.Add(new DevRibbonTraceListener(this));
var configLabel = BuildConfiguration == Release ? "RLS" : "DBG"; /* #no-spell-check-line */
var aotLabel = BuildInfo.IsNativeAot ? "⚡AOT" : "NO AOT";
Tag = $"{configLabel} | {aotLabel}";
TagColor = (BuildConfiguration, BuildInfo.IsNativeAot) switch
{
(Release, true) => ReleaseAotColor,
(Release, false) => ReleaseColor,
(Debug, true) => DebugAotColor,
(Debug, false) => DebugColor,
_ => Colors.Fuchsia,
};
}
public string BuildConfiguration => BuildInfo.Configuration;
public bool IsAotReleaseConfiguration => BuildConfiguration == Release && BuildInfo.IsNativeAot;
public bool IsAot => BuildInfo.IsNativeAot;
public bool IsPublishTrimmed => BuildInfo.PublishTrimmed;
public ObservableCollection<LogEntryViewModel> LatestLogs { get; } = [];
[ObservableProperty]
public partial int WarningCount { get; private set; }
[ObservableProperty]
public partial int ErrorCount { get; private set; }
[ObservableProperty]
public partial string Tag { get; private set; }
[ObservableProperty]
public partial Color TagColor { get; private set; }
[RelayCommand]
private async Task OpenLogFileAsync()
{
var logPath = Logger.CurrentLogFile;
if (File.Exists(logPath))
{
await Launcher.LaunchUriAsync(new Uri(logPath));
}
}
[RelayCommand]
private async Task OpenLogFolderAsync()
{
var logFolderPath = Logger.CurrentVersionLogDirectoryPath;
if (Directory.Exists(logFolderPath))
{
await Launcher.LaunchFolderPathAsync(logFolderPath);
}
}
[RelayCommand]
private void ResetErrorCounters()
{
WarningCount = 0;
ErrorCount = 0;
LatestLogs.Clear();
}
private sealed partial class DevRibbonTraceListener(DevRibbonViewModel viewModel) : TraceListener
{
private const string TimestampFormat = "yyyy-MM-dd HH:mm:ss.fff";
[GeneratedRegex(@"^\[(?<timestamp>.*?)\] \[(?<severity>.*?)\] (?<message>.*)")]
private static partial Regex LogRegex();
private readonly Lock _lock = new();
private LogEntryViewModel? _latestLogEntry;
public override void Write(string? message)
{
// Not required for this scenario.
}
public override void WriteLine(string? message)
{
if (message is null)
{
return;
}
lock (_lock)
{
var match = LogRegex().Match(message);
if (match.Success)
{
var severity = match.Groups["severity"].Value;
var isWarning = severity.Equals("Warning", StringComparison.OrdinalIgnoreCase);
var isError = severity.Equals("Error", StringComparison.OrdinalIgnoreCase);
if (isWarning || isError)
{
var timestampStr = match.Groups["timestamp"].Value;
var timestamp = DateTimeOffset.TryParseExact(
timestampStr,
TimestampFormat,
CultureInfo.InvariantCulture,
DateTimeStyles.AssumeLocal,
out var parsed)
? parsed
: DateTimeOffset.Now;
var logEntry = new LogEntryViewModel(
timestamp,
severity,
match.Groups["message"].Value,
string.Empty);
_latestLogEntry = logEntry;
viewModel._dispatcherQueue.TryEnqueue(() =>
{
if (isWarning)
{
viewModel.WarningCount++;
}
else
{
viewModel.ErrorCount++;
}
viewModel.LatestLogs.Insert(0, logEntry);
while (viewModel.LatestLogs.Count > MaxLogEntries)
{
viewModel.LatestLogs.RemoveAt(viewModel.LatestLogs.Count - 1);
}
});
}
else
{
_latestLogEntry = null;
}
return;
}
if (IndentLevel > 0 && _latestLogEntry is { } latest)
{
viewModel._dispatcherQueue.TryEnqueue(() =>
{
latest.AppendDetails(message);
});
}
}
}
}
}

View File

@@ -1,77 +0,0 @@
// 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.Globalization;
using CommunityToolkit.Mvvm.ComponentModel;
namespace Microsoft.CmdPal.UI.ViewModels;
internal sealed partial class LogEntryViewModel : ObservableObject
{
private const int HeaderMaxLength = 80;
private const string WarningGlyph = "\uE7BA";
private const string ErrorGlyph = "\uEA39";
private const string TimestampFormat = "HH:mm:ss";
private DateTimeOffset Timestamp { get; }
private string Severity { get; }
private string Message { get; }
private string FormattedTimestamp { get; }
public string SeverityGlyph { get; }
[ObservableProperty]
public partial string Header { get; private set; }
[ObservableProperty]
public partial string Description { get; private set; }
[ObservableProperty]
public partial string Details { get; private set; }
public LogEntryViewModel(DateTimeOffset timestamp, string severity, string message, string details)
{
Timestamp = timestamp;
Severity = severity;
Message = message;
Details = details;
SeverityGlyph = severity.ToUpperInvariant() switch
{
"WARNING" => WarningGlyph,
"ERROR" => ErrorGlyph,
_ => string.Empty,
};
FormattedTimestamp = timestamp.ToString(TimestampFormat, CultureInfo.CurrentCulture);
Description = $"{FormattedTimestamp} • {Message}";
Header = Message;
}
public void AppendDetails(string? message)
{
if (string.IsNullOrEmpty(message))
{
return;
}
Details += Environment.NewLine + message;
// Make header the second line of details (because that's actually the message itself):
var detailsLines = Details.Split([Environment.NewLine], StringSplitOptions.None);
if (detailsLines.Length < 2)
{
return;
}
Header = detailsLines[1].Trim();
if (Header.Length > HeaderMaxLength)
{
Header = Header[..(HeaderMaxLength - 1)] + "…";
}
}
}

View File

@@ -1,125 +0,0 @@
// 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.Globalization;
using System.Reflection;
using System.Text;
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
[TestClass]
public class FallbackRemoteDesktopItemTests
{
private static readonly CompositeFormat OpenHostCompositeFormat = CompositeFormat.Parse(Resources.remotedesktop_open_host);
[TestMethod]
public void UpdateQuery_WhenMatchingConnectionExists_UsesConnectionName()
{
var connectionName = "my-rdp-server";
// Arrange
var setup = CreateFallback(connectionName);
var fallback = setup.Fallback;
// Act
fallback.UpdateQuery("my-rdp-server");
// Assert
Assert.AreEqual(connectionName, fallback.Title);
var expectedSubtitle = string.Format(CultureInfo.CurrentCulture, OpenHostCompositeFormat, connectionName);
Assert.AreEqual(expectedSubtitle, fallback.Subtitle);
var command = fallback.Command as OpenRemoteDesktopCommand;
Assert.IsNotNull(command);
Assert.AreEqual(Resources.remotedesktop_command_connect, command.Name);
Assert.AreEqual(connectionName, GetCommandHost(command));
}
[TestMethod]
public void UpdateQuery_WhenQueryIsValidHostWithoutExistingConnection_UsesQuery()
{
// Arrange
var setup = CreateFallback();
var fallback = setup.Fallback;
const string hostname = "test.corp";
// Act
fallback.UpdateQuery(hostname);
// Assert
var expectedTitle = string.Format(CultureInfo.CurrentCulture, OpenHostCompositeFormat, hostname);
Assert.AreEqual(expectedTitle, fallback.Title);
Assert.AreEqual(Resources.remotedesktop_title, fallback.Subtitle);
var command = fallback.Command as OpenRemoteDesktopCommand;
Assert.IsNotNull(command);
Assert.AreEqual(Resources.remotedesktop_command_connect, command.Name);
Assert.AreEqual(hostname, GetCommandHost(command));
}
[TestMethod]
public void UpdateQuery_WhenQueryIsWhitespace_ResetsCommand()
{
// Arrange
var setup = CreateFallback("rdp-server-two");
var fallback = setup.Fallback;
// Act
fallback.UpdateQuery(" ");
// Assert
Assert.AreEqual(Resources.remotedesktop_command_open, fallback.Title);
Assert.AreEqual(string.Empty, fallback.Subtitle);
var command = fallback.Command as OpenRemoteDesktopCommand;
Assert.IsNotNull(command);
Assert.AreEqual(Resources.remotedesktop_command_open, command.Name);
Assert.AreEqual(string.Empty, GetCommandHost(command));
}
[TestMethod]
public void UpdateQuery_WhenQueryIsInvalidHost_ClearsCommand()
{
// Arrange
var setup = CreateFallback("rdp-server-three");
var fallback = setup.Fallback;
// Act
fallback.UpdateQuery("not a valid host");
// Assert
Assert.AreEqual(Resources.remotedesktop_command_open, fallback.Title);
Assert.AreEqual(string.Empty, fallback.Subtitle);
var command = fallback.Command as OpenRemoteDesktopCommand;
Assert.IsNotNull(command);
Assert.AreEqual(Resources.remotedesktop_command_open, command.Name);
Assert.AreEqual(string.Empty, GetCommandHost(command));
}
private static string GetCommandHost(OpenRemoteDesktopCommand command)
{
var field = typeof(OpenRemoteDesktopCommand).GetField("_rdpHost", BindingFlags.NonPublic | BindingFlags.Instance);
if (field is null)
{
return string.Empty;
}
return field.GetValue(command) as string ?? string.Empty;
}
private static (FallbackRemoteDesktopItem Fallback, IRdpConnectionsManager Manager) CreateFallback(params string[] connectionNames)
{
var settingsManager = new MockSettingsManager(connectionNames);
var connectionsManager = new MockRdpConnectionsManager(settingsManager);
var fallback = new FallbackRemoteDesktopItem(connectionsManager);
return (fallback, connectionsManager);
}
}

View File

@@ -1,24 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<!-- Look at Directory.Build.props in root for common stuff as well -->
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
<RootNamespace>Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests</RootNamespace>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal\tests\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Moq" />
<PackageReference Include="MSTest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.RemoteDesktop\Microsoft.CmdPal.Ext.RemoteDesktop.csproj" />
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,23 +0,0 @@
// 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.Linq;
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
using Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
internal sealed class MockRdpConnectionsManager : IRdpConnectionsManager
{
private readonly List<ConnectionListItem> _connections = new();
public IReadOnlyCollection<ConnectionListItem> Connections => _connections.AsReadOnly();
public MockRdpConnectionsManager(ISettingsInterface settingsManager)
{
_connections.AddRange(settingsManager.PredefinedConnections.Select(ConnectionHelpers.MapToResult));
}
}

View File

@@ -1,23 +0,0 @@
// 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.CmdPal.Ext.RemoteDesktop.Settings;
using ToolkitSettings = Microsoft.CommandPalette.Extensions.Toolkit.Settings;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
internal sealed class MockSettingsManager : ISettingsInterface
{
private readonly List<string> _connections;
public IReadOnlyCollection<string> PredefinedConnections => _connections;
public ToolkitSettings Settings { get; } = new();
public MockSettingsManager(params string[] predefinedConnections)
{
_connections = new(predefinedConnections);
}
}

View File

@@ -1,52 +0,0 @@
// 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.Linq;
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
[TestClass]
public class RdpConnectionsManagerTests
{
[TestMethod]
public void Constructor_AddsOpenCommandItem()
{
// Act
var manager = new RdpConnectionsManager(new MockSettingsManager(["test.local"]));
// Assert
Assert.IsTrue(manager.Connections.Any(item => string.IsNullOrEmpty(item.ConnectionName)));
}
[TestMethod]
public void FindConnection_ReturnsExactMatch()
{
// Arrange
var connectionName = "rdp-test";
var connection = new ConnectionListItem(connectionName);
// Act
var result = ConnectionHelpers.FindConnection(connectionName, new[] { connection });
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(connectionName, result.ConnectionName);
}
[TestMethod]
public void FindConnection_ReturnsNullForWhitespaceQuery()
{
// Arrange
var connection = new ConnectionListItem("rdp-test");
// Act
var result = ConnectionHelpers.FindConnection(" ", new[] { connection });
// Assert
Assert.IsNull(result);
}
}

View File

@@ -1,101 +0,0 @@
// 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.Linq;
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
using Microsoft.CmdPal.Ext.RemoteDesktop.Pages;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests;
[TestClass]
public class RemoteDesktopCommandProviderTests
{
[TestMethod]
public void ProviderHasCorrectId()
{
// Setup
var provider = new RemoteDesktopCommandProvider();
// Assert
Assert.AreEqual("com.microsoft.cmdpal.builtin.remotedesktop", provider.Id);
}
[TestMethod]
public void ProviderHasDisplayName()
{
// Setup
var provider = new RemoteDesktopCommandProvider();
// Assert
Assert.IsNotNull(provider.DisplayName);
Assert.IsTrue(provider.DisplayName.Length > 0);
}
[TestMethod]
public void ProviderHasIcon()
{
// Setup
var provider = new RemoteDesktopCommandProvider();
// Assert
Assert.IsNotNull(provider.Icon);
}
[TestMethod]
public void TopLevelCommandsNotEmpty()
{
// Setup
var provider = new RemoteDesktopCommandProvider();
// Act
var commands = provider.TopLevelCommands();
// Assert
Assert.IsNotNull(commands);
Assert.IsTrue(commands.Length > 0);
}
[TestMethod]
public void FallbackCommandsNotEmpty()
{
// Setup
var provider = new RemoteDesktopCommandProvider();
// Act
var commands = provider.FallbackCommands();
// Assert
Assert.IsNotNull(commands);
Assert.IsTrue(commands.Length > 0);
}
[TestMethod]
public void TopLevelCommandsContainListPageCommand()
{
// Setup
var provider = new RemoteDesktopCommandProvider();
// Act
var commands = provider.TopLevelCommands();
// Assert
Assert.AreEqual(1, commands.Length);
Assert.IsInstanceOfType(commands.Single().Command, typeof(RemoteDesktopListPage));
}
[TestMethod]
public void FallbackCommandsContainFallbackItem()
{
// Setup
var provider = new RemoteDesktopCommandProvider();
// Act
var commands = provider.FallbackCommands();
// Assert
Assert.AreEqual(1, commands.Length);
Assert.IsInstanceOfType(commands.Single(), typeof(FallbackRemoteDesktopItem));
}
}

View File

@@ -18,8 +18,6 @@ public class MockSettingsInterface : ISettingsInterface
public int HistoryItemCount { get; set; }
public string CustomSearchUri { get; }
public IReadOnlyList<HistoryItem> HistoryItems => _historyItems;
public MockSettingsInterface(int historyItemCount = 0, bool globalIfUri = true, List<HistoryItem> mockHistory = null)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,21 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="3" y="3.55078" width="9.36537" height="9.36537" rx="0.720413" fill="url(#paint0_linear_2155_27162)"/>
<rect x="1" y="2" width="13" height="9" rx="0.722222" fill="url(#paint1_linear_2155_27162)"/>
<circle cx="11.4" cy="9.4" r="4.4" fill="url(#paint2_radial_2155_27162)"/>
<path d="M13.8703 11.2497C13.964 11.3434 14.116 11.3434 14.2097 11.2497C14.3034 11.156 14.3034 11.004 14.2097 10.9103L12.4594 9.16L14.2097 7.40971C14.3034 7.31598 14.3034 7.16402 14.2097 7.07029C14.116 6.97657 13.964 6.97657 13.8703 7.07029L11.9503 8.9903C11.8566 9.08402 11.8566 9.23598 11.9503 9.32971L13.8703 11.2497ZM9.40971 8.0303C9.31598 7.93657 9.16402 7.93657 9.07029 8.0303C8.97657 8.12402 8.97657 8.27598 9.07029 8.36971L10.8206 10.12L9.07029 11.8703C8.97657 11.964 8.97657 12.116 9.07029 12.2097C9.16402 12.3034 9.31598 12.3034 9.40971 12.2097L11.3297 10.2897C11.4234 10.196 11.4234 10.044 11.3297 9.95031L9.40971 8.0303Z" fill="#666666" stroke="#666666" stroke-width="0.146667"/>
<defs>
<linearGradient id="paint0_linear_2155_27162" x1="3.22298" y1="3.55078" x2="8.52487" y2="6.68847" gradientUnits="userSpaceOnUse">
<stop stop-color="#246FB0"/>
<stop offset="1" stop-color="#14518A"/>
</linearGradient>
<linearGradient id="paint1_linear_2155_27162" x1="1.15476" y1="1.66667" x2="14.867" y2="9.90553" gradientUnits="userSpaceOnUse">
<stop stop-color="#86D6F9"/>
<stop offset="1" stop-color="#1FA3E4"/>
</linearGradient>
<radialGradient id="paint2_radial_2155_27162" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.9111 6.22222) rotate(90) scale(7.57778)">
<stop stop-color="#E7ECF1"/>
<stop offset="0.84" stop-color="#D2D4D6"/>
<stop offset="1" stop-color="#A9ABAC"/>
</radialGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -1,35 +0,0 @@
// 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.Globalization;
using System.Text;
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
internal sealed partial class ConnectionListItem : ListItem
{
public ConnectionListItem(string connectionName)
{
ConnectionName = connectionName;
if (string.IsNullOrEmpty(connectionName))
{
Title = Resources.remotedesktop_open_rdp;
Subtitle = Resources.remotedesktop_subtitle;
}
else
{
Title = connectionName;
CompositeFormat remoteDesktopOpenHostFormat = CompositeFormat.Parse(Resources.remotedesktop_open_host);
Subtitle = string.Format(CultureInfo.CurrentCulture, remoteDesktopOpenHostFormat, connectionName);
}
Icon = Icons.RDPIcon;
Command = new OpenRemoteDesktopCommand(connectionName);
}
public string ConnectionName { get; }
}

View File

@@ -1,74 +0,0 @@
// 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.Globalization;
using System.Linq;
using System.Text;
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
internal sealed partial class FallbackRemoteDesktopItem : FallbackCommandItem
{
private const string _id = "com.microsoft.cmdpal.builtin.remotedesktop.fallback";
private static readonly UriHostNameType[] ValidUriHostNameTypes = [
UriHostNameType.IPv6,
UriHostNameType.IPv4,
UriHostNameType.Dns
];
private static readonly CompositeFormat RemoteDesktopOpenHostFormat = CompositeFormat.Parse(Resources.remotedesktop_open_host);
private readonly IRdpConnectionsManager _rdpConnectionsManager;
public FallbackRemoteDesktopItem(IRdpConnectionsManager rdpConnectionsManager)
: base(new OpenRemoteDesktopCommand(string.Empty), Resources.remotedesktop_title)
{
_rdpConnectionsManager = rdpConnectionsManager;
Title = string.Empty;
Subtitle = string.Empty;
Icon = Icons.RDPIcon;
}
public override void UpdateQuery(string query)
{
if (string.IsNullOrWhiteSpace(query))
{
Title = string.Empty;
Subtitle = string.Empty;
Command = new OpenRemoteDesktopCommand(string.Empty);
return;
}
var connections = _rdpConnectionsManager.Connections.Where(w => !string.IsNullOrWhiteSpace(w.ConnectionName));
var queryConnection = ConnectionHelpers.FindConnection(query, connections);
if (queryConnection is not null && !string.IsNullOrWhiteSpace(queryConnection.ConnectionName))
{
var connectionName = queryConnection.ConnectionName;
Command = new OpenRemoteDesktopCommand(connectionName);
Title = connectionName;
Subtitle = string.Format(CultureInfo.CurrentCulture, RemoteDesktopOpenHostFormat, connectionName);
}
else if (ValidUriHostNameTypes.Contains(Uri.CheckHostName(query)))
{
var connectionName = query.Trim();
Command = new OpenRemoteDesktopCommand(connectionName);
Title = string.Format(CultureInfo.CurrentCulture, RemoteDesktopOpenHostFormat, connectionName);
Subtitle = Resources.remotedesktop_title;
}
else
{
Title = string.Empty;
Subtitle = string.Empty;
Command = new OpenRemoteDesktopCommand(string.Empty);
}
}
}

View File

@@ -1,82 +0,0 @@
// 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.Diagnostics;
using System.Text;
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
internal sealed partial class OpenRemoteDesktopCommand : BaseObservable, IInvokableCommand
{
private static readonly CompositeFormat ProcessErrorFormat =
CompositeFormat.Parse(Resources.remotedesktop_log_mstsc_error);
private static readonly CompositeFormat InvalidHostnameFormat =
CompositeFormat.Parse(Resources.remotedesktop_log_invalid_hostname);
public string Name { get; }
public string Id { get; } = "com.microsoft.cmdpal.builtin.remotedesktop.openrdp";
public IIconInfo Icon => Icons.RDPIcon;
private readonly string _rdpHost;
public OpenRemoteDesktopCommand(string rdpHost)
{
_rdpHost = rdpHost;
Name = string.IsNullOrWhiteSpace(_rdpHost) ?
Resources.remotedesktop_command_open :
Resources.remotedesktop_command_connect;
}
public ICommandResult Invoke(object sender)
{
using var process = new Process();
process.StartInfo.UseShellExecute = false;
process.StartInfo.WorkingDirectory = Environment.SpecialFolder.MyDocuments.ToString();
process.StartInfo.FileName = "mstsc";
if (!string.IsNullOrWhiteSpace(_rdpHost))
{
// validate that _rdpHost is a proper hostname or IP address
if (Uri.CheckHostName(_rdpHost) == UriHostNameType.Unknown)
{
return CommandResult.ShowToast(new ToastArgs()
{
Message = string.Format(
System.Globalization.CultureInfo.CurrentCulture,
InvalidHostnameFormat,
_rdpHost),
Result = CommandResult.KeepOpen(),
});
}
process.StartInfo.Arguments = $"/v:{_rdpHost}";
}
try
{
process.Start();
return CommandResult.Dismiss();
}
catch (Exception ex)
{
return CommandResult.ShowToast(new ToastArgs()
{
Message = string.Format(
System.Globalization.CultureInfo.CurrentCulture,
ProcessErrorFormat,
ex.Message),
Result = CommandResult.KeepOpen(),
});
}
}
}

View File

@@ -1,30 +0,0 @@
// 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.Linq;
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
internal static class ConnectionHelpers
{
public static ConnectionListItem MapToResult(string item) => new(item);
public static ConnectionListItem? FindConnection(string query, IEnumerable<ConnectionListItem> connections)
{
if (string.IsNullOrWhiteSpace(query))
{
return null;
}
var matchedConnection = ListHelpers.FilterList(
connections,
query,
(s, i) => ListHelpers.ScoreListItem(s, i))
.FirstOrDefault();
return matchedConnection;
}
}

View File

@@ -1,13 +0,0 @@
// 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.CmdPal.Ext.RemoteDesktop.Commands;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
internal interface IRdpConnectionsManager
{
IReadOnlyCollection<ConnectionListItem> Connections { get; }
}

View File

@@ -1,89 +0,0 @@
// 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.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using Microsoft.CmdPal.Ext.RemoteDesktop.Commands;
using Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
using Microsoft.Win32;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
internal class RdpConnectionsManager : IRdpConnectionsManager
{
private readonly ISettingsInterface _settingsManager;
private readonly ConnectionListItem _openRdpCommandListItem = new(string.Empty);
private ReadOnlyCollection<ConnectionListItem> _connections = new(Array.Empty<ConnectionListItem>());
private const int MinutesToCache = 1;
private DateTime? _connectionsLastLoaded;
public RdpConnectionsManager(ISettingsInterface settingsManager)
{
_settingsManager = settingsManager;
_settingsManager.Settings.SettingsChanged += (s, e) =>
{
_connectionsLastLoaded = null;
};
}
public IReadOnlyCollection<ConnectionListItem> Connections
{
get
{
if (!_connectionsLastLoaded.HasValue ||
(DateTime.Now - _connectionsLastLoaded.Value).TotalMinutes >= MinutesToCache)
{
var registryConnections = GetRdpConnectionsFromRegistry();
var predefinedConnections = GetPredefinedConnectionsFromSettings();
_connectionsLastLoaded = DateTime.Now;
var newConnections = new List<ConnectionListItem>(registryConnections.Count + predefinedConnections.Count + 1);
newConnections.AddRange(registryConnections);
newConnections.AddRange(predefinedConnections);
newConnections.Insert(0, _openRdpCommandListItem);
Interlocked.Exchange(ref _connections, new ReadOnlyCollection<ConnectionListItem>(newConnections));
}
return _connections;
}
}
private List<ConnectionListItem> GetRdpConnectionsFromRegistry()
{
using var key = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Microsoft\Terminal Server Client\Default");
var validConnections = new List<ConnectionListItem>();
if (key is not null)
{
validConnections = key.GetValueNames()
.Select(name => key.GetValue(name))
.OfType<string>() // Keep only string values
.Select(v => v.Trim()) // Normalize
.Where(v => !string.IsNullOrWhiteSpace(v))
.Distinct() // Remove dupes if any
.Select(ConnectionHelpers.MapToResult)
.ToList();
}
return validConnections;
}
private List<ConnectionListItem> GetPredefinedConnectionsFromSettings()
{
var validConnections = _settingsManager.PredefinedConnections
.Select(s => s.Trim())
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(ConnectionHelpers.MapToResult)
.ToList();
return validConnections;
}
}

View File

@@ -1,12 +0,0 @@
// 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.RemoteDesktop;
internal static class Icons
{
internal static IconInfo RDPIcon { get; } = IconHelpers.FromRelativePath("Assets\\RemoteDesktop.svg");
}

View File

@@ -1,44 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<Import Project="..\..\..\..\Common.Dotnet.AotCompatibility.props" />
<Import Project="..\Common.ExtDependencies.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.RemoteDesktop</RootNamespace>
<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.RemoteDesktop.pri</ProjectPriFileName>
<nullable>enable</nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<!-- CmdPal Toolkit reference now included via Common.ExtDependencies.props -->
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Assets\" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\RemoteDesktop.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\RemoteDesktop.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@@ -1,27 +0,0 @@
// 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.Linq;
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Pages;
internal sealed partial class RemoteDesktopListPage : ListPage
{
private readonly IRdpConnectionsManager _rdpConnectionsManager;
public RemoteDesktopListPage(IRdpConnectionsManager rdpConnectionsManager)
{
Icon = Icons.RDPIcon;
Name = Resources.remotedesktop_title;
Id = "com.microsoft.cmdpal.builtin.remotedesktop";
_rdpConnectionsManager = rdpConnectionsManager;
}
public override IListItem[] GetItems() => _rdpConnectionsManager.Connections.ToArray();
}

View File

@@ -1,7 +0,0 @@
// 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.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.CmdPal.Ext.RemoteDesktop.UnitTests")]

View File

@@ -1,153 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Ext.RemoteDesktop.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Connect.
/// </summary>
public static string remotedesktop_command_connect {
get {
return ResourceManager.GetString("remotedesktop_command_connect", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open.
/// </summary>
public static string remotedesktop_command_open {
get {
return ResourceManager.GetString("remotedesktop_command_open", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The hostname &apos;{0}&apos; was invalid. Ensure you&apos;re using a valid hostname or IP address..
/// </summary>
public static string remotedesktop_log_invalid_hostname {
get {
return ResourceManager.GetString("remotedesktop_log_invalid_hostname", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unable to initialize Microsoft Terminal Service Client. Ensure it is enabled in Windows Settings.\r{0}.
/// </summary>
public static string remotedesktop_log_mstsc_error {
get {
return ResourceManager.GetString("remotedesktop_log_mstsc_error", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connect to {0}.
/// </summary>
public static string remotedesktop_open_host {
get {
return ResourceManager.GetString("remotedesktop_open_host", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Open Remote Desktop Client.
/// </summary>
public static string remotedesktop_open_rdp {
get {
return ResourceManager.GetString("remotedesktop_open_rdp", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A list of connections to include in the query results by default.
/// </summary>
public static string remotedesktop_settings_predefined_connections_description {
get {
return ResourceManager.GetString("remotedesktop_settings_predefined_connections_description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Predefined connections.
/// </summary>
public static string remotedesktop_settings_predefined_connections_title {
get {
return ResourceManager.GetString("remotedesktop_settings_predefined_connections_title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Establish Remote Desktop connections.
/// </summary>
public static string remotedesktop_subtitle {
get {
return ResourceManager.GetString("remotedesktop_subtitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Remote Desktop.
/// </summary>
public static string remotedesktop_title {
get {
return ResourceManager.GetString("remotedesktop_title", resourceCulture);
}
}
}
}

View File

@@ -1,150 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="remotedesktop_title" xml:space="preserve">
<value>Remote Desktop</value>
</data>
<data name="remotedesktop_subtitle" xml:space="preserve">
<value>Establish Remote Desktop connections</value>
</data>
<data name="remotedesktop_command_open" xml:space="preserve">
<value>Open</value>
</data>
<data name="remotedesktop_open_host" xml:space="preserve">
<value>Connect to {0}</value>
</data>
<data name="remotedesktop_command_connect" xml:space="preserve">
<value>Connect</value>
</data>
<data name="remotedesktop_open_rdp" xml:space="preserve">
<value>Open Remote Desktop Client</value>
</data>
<data name="remotedesktop_settings_predefined_connections_title" xml:space="preserve">
<value>Predefined connections</value>
</data>
<data name="remotedesktop_settings_predefined_connections_description" xml:space="preserve">
<value>A list of connections to include in the query results by default</value>
</data>
<data name="remotedesktop_log_mstsc_error" xml:space="preserve">
<value>Unable to initialize Microsoft Terminal Service Client. Ensure it is enabled in Windows Settings.\r{0}</value>
</data>
<data name="remotedesktop_log_invalid_hostname" xml:space="preserve">
<value>The hostname '{0}' was invalid. Ensure you're using a valid hostname or IP address.</value>
</data>
</root>

View File

@@ -1,45 +0,0 @@
// 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.Ext.RemoteDesktop.Commands;
using Microsoft.CmdPal.Ext.RemoteDesktop.Helper;
using Microsoft.CmdPal.Ext.RemoteDesktop.Pages;
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
using Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.RemoteDesktop;
public partial class RemoteDesktopCommandProvider : CommandProvider
{
private readonly CommandItem listPageCommand;
private readonly FallbackRemoteDesktopItem fallback;
public RemoteDesktopCommandProvider()
{
Id = "com.microsoft.cmdpal.builtin.remotedesktop";
DisplayName = Resources.remotedesktop_title;
Icon = Icons.RDPIcon;
var settingsManager = new SettingsManager();
var rdpConnectionsManager = new RdpConnectionsManager(settingsManager);
var listPage = new RemoteDesktopListPage(rdpConnectionsManager);
fallback = new FallbackRemoteDesktopItem(rdpConnectionsManager);
listPageCommand = new CommandItem(listPage)
{
Subtitle = Resources.remotedesktop_subtitle,
Icon = Icons.RDPIcon,
MoreCommands = [
new CommandContextItem(settingsManager.Settings.SettingsPage),
],
};
}
public override ICommandItem[] TopLevelCommands() => [listPageCommand];
public override IFallbackCommandItem[] FallbackCommands() => [fallback];
}

View File

@@ -1,15 +0,0 @@
// 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 ToolkitSettings = Microsoft.CommandPalette.Extensions.Toolkit.Settings;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
internal interface ISettingsInterface
{
public IReadOnlyCollection<string> PredefinedConnections { get; }
public ToolkitSettings Settings { get; }
}

View File

@@ -1,53 +0,0 @@
// 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.IO;
using System.Linq;
using Microsoft.CmdPal.Ext.RemoteDesktop.Properties;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.RemoteDesktop.Settings;
internal class SettingsManager : JsonSettingsManager, ISettingsInterface
{
// Line break character used in WinUI3 TextBox and TextBlock.
private const char TEXTBOXNEWLINE = '\r';
private static readonly string _namespace = "com.microsoft.cmdpal.builtin.remotedesktop";
private static string Namespaced(string propertyName) => $"{_namespace}.{propertyName}";
private readonly TextSetting _predefinedConnections = new(
Namespaced(nameof(PredefinedConnections)),
Resources.remotedesktop_settings_predefined_connections_title,
Resources.remotedesktop_settings_predefined_connections_description,
string.Empty)
{
Multiline = true,
Placeholder = $"server1.domain.com{TEXTBOXNEWLINE}server2.domain.com{TEXTBOXNEWLINE}192.168.1.1",
};
public IReadOnlyCollection<string> PredefinedConnections => _predefinedConnections.Value?.Split(TEXTBOXNEWLINE).ToList() ?? [];
internal static string SettingsJsonPath()
{
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
Directory.CreateDirectory(directory);
return Path.Combine(directory, "settings.json");
}
public SettingsManager()
{
FilePath = SettingsJsonPath();
Settings.Add(_predefinedConnections);
// Load settings from file upon initialization
LoadSettings();
Settings.SettingsChanged += (s, a) => this.SaveSettings();
}
}

View File

@@ -28,15 +28,12 @@ internal sealed partial class SearchWebCommand : InvokableCommand
public override CommandResult Invoke()
{
var uri = BuildUri();
if (!_browserInfoService.Open(uri))
if (!_browserInfoService.Open($"? {Arguments}"))
{
// TODO GH# 138 --> actually display feedback from the extension somewhere.
return CommandResult.KeepOpen();
}
// remember only the query, not the full URI
if (_settingsManager.HistoryItemCount != 0)
{
_settingsManager.AddHistoryItem(new HistoryItem(Arguments, DateTime.Now));
@@ -44,28 +41,4 @@ internal sealed partial class SearchWebCommand : InvokableCommand
return CommandResult.Dismiss();
}
private string BuildUri()
{
if (string.IsNullOrWhiteSpace(_settingsManager.CustomSearchUri))
{
return $"? " + Arguments;
}
// if the custom search URI contains query placeholder, replace it with the actual query
// otherwise append the query to the end of the URI
// support {query}, %query% or %s as placeholder
var placeholderVariants = new[] { "{query}", "%query%", "%s" };
foreach (var placeholder in placeholderVariants)
{
if (_settingsManager.CustomSearchUri.Contains(placeholder, StringComparison.OrdinalIgnoreCase))
{
return _settingsManager.CustomSearchUri.Replace(placeholder, Uri.EscapeDataString(Arguments), StringComparison.OrdinalIgnoreCase);
}
}
// is this too smart?
var separator = _settingsManager.CustomSearchUri.Contains('?') ? '&' : '?';
return $"{_settingsManager.CustomSearchUri}{separator}q={Uri.EscapeDataString(Arguments)}";
}
}

View File

@@ -18,7 +18,5 @@ public interface ISettingsInterface
public IReadOnlyList<HistoryItem> HistoryItems { get; }
string CustomSearchUri { get; }
public void AddHistoryItem(HistoryItem historyItem);
}

View File

@@ -41,15 +41,6 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
Resources.plugin_global_if_uri,
false);
private readonly TextSetting _customSearchUri = new(
Namespaced(nameof(CustomSearchUri)),
Resources.plugin_custom_search_uri,
Resources.plugin_custom_search_uri,
string.Empty)
{
Placeholder = Resources.plugin_custom_search_uri_placeholder,
};
private readonly ChoiceSetSetting _historyItemCount = new(
Namespaced(HistoryItemCountLegacySettingsKey),
Resources.plugin_history_item_count,
@@ -60,8 +51,6 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
public int HistoryItemCount => int.TryParse(_historyItemCount.Value, out var value) && value >= 0 ? value : 0;
public string CustomSearchUri => _customSearchUri.Value ?? string.Empty;
public IReadOnlyList<HistoryItem> HistoryItems => _history.HistoryItems;
public SettingsManager()
@@ -70,7 +59,6 @@ public class SettingsManager : JsonSettingsManager, ISettingsInterface
Settings.Add(_globalIfURI);
Settings.Add(_historyItemCount);
Settings.Add(_customSearchUri);
LoadSettings();

View File

@@ -159,24 +159,6 @@ namespace Microsoft.CmdPal.Ext.WebSearch.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Custom search engine URL.
/// </summary>
public static string plugin_custom_search_uri {
get {
return ResourceManager.GetString("plugin_custom_search_uri", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use {query} or %s as the search query placeholder; e.g. https://www.bing.com/search?q={query}.
/// </summary>
public static string plugin_custom_search_uri_placeholder {
get {
return ResourceManager.GetString("plugin_custom_search_uri_placeholder", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Searches the web with your default search engine.
/// </summary>

View File

@@ -187,10 +187,4 @@
<data name="default_browser" xml:space="preserve">
<value>default browser</value>
</data>
<data name="plugin_custom_search_uri" xml:space="preserve">
<value>Custom search engine URL</value>
</data>
<data name="plugin_custom_search_uri_placeholder" xml:space="preserve">
<value>Use {query} or %s as the search query placeholder; e.g. https://www.bing.com/search?q={query}</value>
</data>
</root>

View File

@@ -9,6 +9,13 @@ namespace SamplePagesExtension;
internal sealed partial class SampleGalleryListPage : ListPage
{
public SampleGalleryListPage()
{
Icon = new IconInfo("\uE7C5");
Name = "Sample Gallery List Page";
GridProperties = new GalleryGridLayout();
}
public override IListItem[] GetItems()
{
return [

View File

@@ -1,59 +0,0 @@
// 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;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace SamplePagesExtension;
internal sealed partial class SampleGridsListPage : ListPage
{
private readonly IListItem[] _items =
[
new ListItem(new SampleGalleryListPage { GridProperties = new GalleryGridLayout { ShowTitle = true, ShowSubtitle = true } })
{
Title = "Gallery list page (title and subtitle)",
Subtitle = "A sample gallery list page with images",
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
},
new ListItem(new SampleGalleryListPage { GridProperties = new GalleryGridLayout { ShowTitle = true, ShowSubtitle = false } })
{
Title = "Gallery list page (title, no subtitle)",
Subtitle = "A sample gallery list page with images",
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
},
new ListItem(new SampleGalleryListPage { GridProperties = new GalleryGridLayout { ShowTitle = false, ShowSubtitle = false } })
{
Title = "Gallery list page (no title, no subtitle)",
Subtitle = "A sample gallery list page with images",
Icon = IconHelpers.FromRelativePath("Assets/Images/Swirls.png"),
},
new ListItem(new SampleGalleryListPage { GridProperties = new SmallGridLayout() })
{
Title = "Small grid list page",
Subtitle = "A sample grid list page with text items",
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
},
new ListItem(new SampleGalleryListPage { GridProperties = new MediumGridLayout { ShowTitle = true } })
{
Title = "Medium grid (with title)",
Subtitle = "A sample grid list page with text items",
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
},
new ListItem(new SampleGalleryListPage { GridProperties = new MediumGridLayout { ShowTitle = false } })
{
Title = "Medium grid (hidden title)",
Subtitle = "A sample grid list page with text items",
Icon = IconHelpers.FromRelativePath("Assets/Images/Win-Digital.png"),
}
];
public SampleGridsListPage()
{
Icon = new IconInfo("\uE7C5");
Name = "Grid and gallery lists";
}
public override IListItem[] GetItems() => _items;
}

View File

@@ -34,9 +34,9 @@ public partial class SamplesListPage : ListPage
Title = "Dynamic List Page Command",
Subtitle = "Changes the list of items in response to the typed query",
},
new ListItem(new SampleGridsListPage())
new ListItem(new SampleGalleryListPage())
{
Title = "Grid views and galleries",
Title = "Gallery List Page Command",
Subtitle = "Displays items as a gallery",
},
new ListItem(new OnLoadPage())