mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-01-08 05:17:03 +01:00
Compare commits
13 Commits
tools/Rele
...
dev/migrie
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7168e0f63 | ||
|
|
c30310d89f | ||
|
|
ca5264f337 | ||
|
|
ef7e0fff02 | ||
|
|
8b246acce9 | ||
|
|
8bf835b7f6 | ||
|
|
389c46564c | ||
|
|
c8486087d8 | ||
|
|
05c700a4cd | ||
|
|
48b70e0861 | ||
|
|
08dc3fbcef | ||
|
|
eeb84cb621 | ||
|
|
5b2388cd58 |
@@ -327,6 +327,12 @@
|
||||
"WinUI3Apps\\ReverseMarkdown.dll",
|
||||
"WinUI3Apps\\SharpCompress.dll",
|
||||
"WinUI3Apps\\ZstdSharp.dll",
|
||||
"CommunityToolkit.WinUI.Controls.MarkdownTextBlock.dll",
|
||||
"WinUI3Apps\\CommunityToolkit.WinUI.Controls.MarkdownTextBlock.dll",
|
||||
"Markdig.dll",
|
||||
"WinUI3Apps\\Markdig.dll",
|
||||
"RomanNumerals.dll",
|
||||
"WinUI3Apps\\RomanNumerals.dll",
|
||||
"TestableIO.System.IO.Abstractions.dll",
|
||||
"WinUI3Apps\\TestableIO.System.IO.Abstractions.dll",
|
||||
"TestableIO.System.IO.Abstractions.Wrappers.dll",
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<PackageVersion Include="UnicodeInformation" Version="2.6.0" />
|
||||
<PackageVersion Include="UnitsNet" Version="5.56.0" />
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.6.0" />
|
||||
<PackageVersion Include="WinUIEx" Version="2.2.0" />
|
||||
<PackageVersion Include="WinUIEx" Version="2.8.0" />
|
||||
<PackageVersion Include="WPF-UI" Version="3.0.5" />
|
||||
<PackageVersion Include="WyHash" Version="1.0.5" />
|
||||
<PackageVersion Include="WixToolset.Heat" Version="5.0.2" />
|
||||
|
||||
@@ -811,6 +811,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.WebSea
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.Shell.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.Shell.UnitTests\Microsoft.CmdPal.Ext.Shell.UnitTests.csproj", "{E816D7B4-4688-4ECB-97CC-3D8E798F3833}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests", "src\modules\cmdpal\Tests\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests\Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests.csproj", "{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
@@ -2945,6 +2947,14 @@ Global
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.ActiveCfg = Release|x64
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833}.Release|x64.Build.0 = Release|x64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Debug|x64.Build.0 = Debug|x64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Release|x64.ActiveCfg = Release|x64
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -3267,6 +3277,7 @@ Global
|
||||
{E816D7B3-4688-4ECB-97CC-3D8E798F3832} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B2-4688-4ECB-97CC-3D8E798F3831} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{E816D7B4-4688-4ECB-97CC-3D8E798F3833} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
{4E0FCF69-B06B-D272-76BF-ED3A559B4EDA} = {8EF25507-2575-4ADE-BF7E-D23376903AB8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0}
|
||||
|
||||
@@ -1,5 +1,64 @@
|
||||
<?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">
|
||||
|
||||
@@ -2,9 +2,17 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Helpers;
|
||||
|
||||
internal static class BindTransformers
|
||||
{
|
||||
public static bool Negate(bool value) => !value;
|
||||
|
||||
public static Visibility EmptyToCollapsed(string? input)
|
||||
=> string.IsNullOrEmpty(input) ? Visibility.Collapsed : Visibility.Visible;
|
||||
|
||||
public static Visibility EmptyOrWhitespaceToCollapsed(string? input)
|
||||
=> string.IsNullOrWhiteSpace(input) ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
|
||||
@@ -41,6 +41,31 @@
|
||||
FalseValue="Visible"
|
||||
TrueValue="Collapsed" />
|
||||
|
||||
<Style
|
||||
x:Key="DetailKeyTextBlockStyle"
|
||||
BasedOn="{StaticResource CaptionTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="TextWrapping" Value="WrapWholeWords" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="SeparatorKeyTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyStrongTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="TextWrapping" Value="WrapWholeWords" />
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="DetailValueTextBlockStyle"
|
||||
BasedOn="{StaticResource BodyTextBlockStyle}"
|
||||
TargetType="TextBlock">
|
||||
<Setter Property="IsTextSelectionEnabled" Value="True" />
|
||||
<Setter Property="TextWrapping" Value="WrapWholeWords" />
|
||||
</Style>
|
||||
|
||||
<DataTemplate x:Key="TagTemplate" x:DataType="coreViewModels:TagViewModel">
|
||||
<cpcontrols:Tag
|
||||
HorizontalAlignment="Left"
|
||||
@@ -68,7 +93,7 @@
|
||||
Margin="0,3,8,0"
|
||||
SourceKey="{x:Bind Icon, Mode=OneWay}"
|
||||
SourceRequested="{x:Bind help:IconCacheProvider.SourceRequested}" />
|
||||
<TextBlock Text="{x:Bind Name}" />
|
||||
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{x:Bind Name}" />
|
||||
</StackPanel>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
@@ -76,20 +101,13 @@
|
||||
|
||||
<DataTemplate x:Key="DetailsLinkTemplate" x:DataType="coreViewModels:DetailsLinkViewModel">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Style="{StaticResource DetailKeyTextBlockStyle}" Text="{x:Bind Key, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind Key, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
IsTextSelectionEnabled="True"
|
||||
Style="{StaticResource DetailValueTextBlockStyle}"
|
||||
Text="{x:Bind Text, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords"
|
||||
Visibility="{x:Bind IsText, Mode=OneWay}" />
|
||||
<HyperlinkButton
|
||||
Padding="0"
|
||||
FontSize="12"
|
||||
NavigateUri="{x:Bind Link, Mode=OneWay}"
|
||||
Visibility="{x:Bind IsLink, Mode=OneWay}">
|
||||
<TextBlock Text="{x:Bind Text, Mode=OneWay}" TextWrapping="Wrap" />
|
||||
@@ -98,10 +116,7 @@
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="DetailsCommandsTemplate" x:DataType="coreViewModels:DetailsCommandsViewModel">
|
||||
<StackPanel Orientation="Vertical" Spacing="4">
|
||||
<TextBlock
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind Key, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock Style="{StaticResource DetailKeyTextBlockStyle}" Text="{x:Bind Key, Mode=OneWay}" />
|
||||
<ItemsControl
|
||||
ItemTemplate="{StaticResource CommandTemplate}"
|
||||
ItemsSource="{x:Bind Commands, Mode=OneWay}"
|
||||
@@ -111,24 +126,20 @@
|
||||
<DataTemplate x:Key="DetailsSeparatorTemplate" x:DataType="coreViewModels:DetailsSeparatorViewModel">
|
||||
<StackPanel Margin="0,8,8,0" Orientation="Vertical">
|
||||
<Border
|
||||
Margin="8,0,0,0"
|
||||
BorderBrush="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Margin="0,0,0,0"
|
||||
BorderBrush="{ThemeResource DividerStrokeColorDefaultBrush}"
|
||||
BorderThickness="0,0,0,2">
|
||||
<TextBlock
|
||||
Margin="-8,0,0,8"
|
||||
FontWeight="SemiBold"
|
||||
IsTextSelectionEnabled="True"
|
||||
Margin="0,0,0,0"
|
||||
Style="{StaticResource SeparatorKeyTextBlockStyle}"
|
||||
Text="{x:Bind Key, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
Visibility="{x:Bind help:BindTransformers.EmptyOrWhitespaceToCollapsed(Key), FallbackValue=Collapsed}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
<DataTemplate x:Key="DetailsTagsTemplate" x:DataType="coreViewModels:DetailsTagsViewModel">
|
||||
<StackPanel Orientation="Vertical" Spacing="4">
|
||||
<TextBlock
|
||||
IsTextSelectionEnabled="True"
|
||||
Text="{x:Bind Key, Mode=OneWay}"
|
||||
TextWrapping="WrapWholeWords" />
|
||||
<TextBlock Style="{StaticResource DetailKeyTextBlockStyle}" Text="{x:Bind Key, Mode=OneWay}" />
|
||||
<ItemsControl
|
||||
ItemTemplate="{StaticResource TagTemplate}"
|
||||
ItemsSource="{x:Bind Tags, Mode=OneWay}"
|
||||
|
||||
@@ -24,10 +24,11 @@
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TitleBar x:Name="TitleBar">
|
||||
<TitleBar x:Name="AppTitleBar" PaneToggleRequested="AppTitleBar_PaneToggleRequested">
|
||||
<!-- This is a workaround for https://github.com/microsoft/microsoft-ui-xaml/issues/10374, once fixed we should just be using IconSource -->
|
||||
<TitleBar.LeftHeader>
|
||||
<ImageIcon
|
||||
x:Name="WorkAroundIcon"
|
||||
Height="16"
|
||||
Margin="16,0,0,0"
|
||||
Source="ms-appx:///Assets/icon.svg" />
|
||||
@@ -36,18 +37,19 @@
|
||||
<NavigationView
|
||||
x:Name="NavView"
|
||||
Grid.Row="1"
|
||||
CompactModeThresholdWidth="1007"
|
||||
DisplayModeChanged="NavView_DisplayModeChanged"
|
||||
ExpandedModeThresholdWidth="1007"
|
||||
IsBackButtonVisible="Collapsed"
|
||||
IsPaneToggleButtonVisible="False"
|
||||
IsSettingsVisible="False"
|
||||
ItemInvoked="NavView_ItemInvoked"
|
||||
Loaded="NavView_Loaded"
|
||||
OpenPaneLength="200">
|
||||
Loaded="NavView_Loaded">
|
||||
<NavigationView.Resources>
|
||||
<SolidColorBrush x:Key="NavigationViewContentBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="NavigationViewContentGridBorderBrush" Color="Transparent" />
|
||||
<Thickness x:Key="NavigationViewHeaderMargin">15,0,0,0</Thickness>
|
||||
</NavigationView.Resources>
|
||||
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem
|
||||
x:Uid="Settings_GeneralPage_NavigationViewItem_General"
|
||||
@@ -58,34 +60,32 @@
|
||||
Icon="{ui:FontIcon Glyph=}"
|
||||
Tag="Extensions" />
|
||||
</NavigationView.MenuItems>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<BreadcrumbBar
|
||||
x:Name="NavigationBreadcrumbBar"
|
||||
Grid.Row="0"
|
||||
MaxWidth="1000"
|
||||
ItemClicked="NavigationBreadcrumbBar_ItemClicked"
|
||||
ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}">
|
||||
<BreadcrumbBar.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:Crumb">
|
||||
<TextBlock Text="{x:Bind Label, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</BreadcrumbBar.ItemTemplate>
|
||||
<BreadcrumbBar.Resources>
|
||||
<ResourceDictionary>
|
||||
<x:Double x:Key="BreadcrumbBarItemThemeFontSize">28</x:Double>
|
||||
<Thickness x:Key="BreadcrumbBarChevronPadding">7,4,8,0</Thickness>
|
||||
<FontWeight x:Key="BreadcrumbBarItemFontWeight">SemiBold</FontWeight>
|
||||
<x:Double x:Key="BreadcrumbBarChevronFontSize">16</x:Double>
|
||||
</ResourceDictionary>
|
||||
</BreadcrumbBar.Resources>
|
||||
</BreadcrumbBar>
|
||||
|
||||
<Grid Padding="16,0">
|
||||
<BreadcrumbBar
|
||||
x:Name="NavigationBreadcrumbBar"
|
||||
MaxWidth="1000"
|
||||
ItemClicked="NavigationBreadcrumbBar_ItemClicked"
|
||||
ItemsSource="{x:Bind BreadCrumbs, Mode=OneWay}">
|
||||
<BreadcrumbBar.ItemTemplate>
|
||||
<DataTemplate x:DataType="local:Crumb">
|
||||
<TextBlock Text="{x:Bind Label, Mode=OneWay}" />
|
||||
</DataTemplate>
|
||||
</BreadcrumbBar.ItemTemplate>
|
||||
<BreadcrumbBar.Resources>
|
||||
<ResourceDictionary>
|
||||
<x:Double x:Key="BreadcrumbBarItemThemeFontSize">28</x:Double>
|
||||
<Thickness x:Key="BreadcrumbBarChevronPadding">7,4,8,0</Thickness>
|
||||
<FontWeight x:Key="BreadcrumbBarItemFontWeight">SemiBold</FontWeight>
|
||||
<x:Double x:Key="BreadcrumbBarChevronFontSize">16</x:Double>
|
||||
</ResourceDictionary>
|
||||
</BreadcrumbBar.Resources>
|
||||
</BreadcrumbBar>
|
||||
</Grid>
|
||||
<Frame x:Name="NavFrame" Grid.Row="1" />
|
||||
</Grid>
|
||||
</NavigationView>
|
||||
|
||||
@@ -14,6 +14,7 @@ using Microsoft.UI.Xaml.Automation.Peers;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using WinUIEx;
|
||||
using RS_ = Microsoft.CmdPal.UI.Helpers.ResourceLoaderInstance;
|
||||
using TitleBar = Microsoft.UI.Xaml.Controls.TitleBar;
|
||||
|
||||
namespace Microsoft.CmdPal.UI.Settings;
|
||||
|
||||
@@ -34,7 +35,7 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
var title = RS_.GetString("SettingsWindowTitle");
|
||||
this.AppWindow.Title = title;
|
||||
this.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall;
|
||||
this.TitleBar.Title = title;
|
||||
this.AppTitleBar.Title = title;
|
||||
PositionCentered();
|
||||
|
||||
WeakReferenceMessenger.Default.Register<NavigateToExtensionSettingsMessage>(this);
|
||||
@@ -142,11 +143,13 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
{
|
||||
if (args.DisplayMode == NavigationViewDisplayMode.Compact || args.DisplayMode == NavigationViewDisplayMode.Minimal)
|
||||
{
|
||||
NavView.IsPaneToggleButtonVisible = false;
|
||||
AppTitleBar.IsPaneToggleButtonVisible = true;
|
||||
WorkAroundIcon.Margin = new Thickness(8, 0, 16, 0); // Required for workaround, see XAML comment
|
||||
}
|
||||
else
|
||||
{
|
||||
NavView.IsPaneToggleButtonVisible = true;
|
||||
AppTitleBar.IsPaneToggleButtonVisible = false;
|
||||
WorkAroundIcon.Margin = new Thickness(16, 0, 0, 0); // Required for workaround, see XAML comment
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +158,11 @@ public sealed partial class SettingsWindow : WindowEx,
|
||||
// This might come in on a background thread
|
||||
DispatcherQueue.TryEnqueue(() => Close());
|
||||
}
|
||||
|
||||
private void AppTitleBar_PaneToggleRequested(TitleBar sender, object args)
|
||||
{
|
||||
NavView.IsPaneOpen = !NavView.IsPaneOpen;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct Crumb
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
<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>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests</RootNamespace>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\ext\Microsoft.CmdPal.Ext.ClipboardHistory\Microsoft.CmdPal.Ext.ClipboardHistory.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.CmdPal.Ext.UnitTestsBase\Microsoft.CmdPal.Ext.UnitTestBase.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,274 @@
|
||||
// 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 Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.UnitTests;
|
||||
|
||||
[TestClass]
|
||||
public class UrlHelperTests
|
||||
{
|
||||
[TestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
[DataRow("\t")]
|
||||
[DataRow("\r\n")]
|
||||
public void IsValidUrl_ReturnsFalse_WhenUrlIsNullOrWhitespace(string url)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("test\nurl")]
|
||||
[DataRow("test\rurl")]
|
||||
[DataRow("http://example.com\nmalicious")]
|
||||
[DataRow("https://test.com\r\nheader")]
|
||||
public void IsValidUrl_ReturnsFalse_WhenUrlContainsNewlines(string url)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("com")]
|
||||
[DataRow("org")]
|
||||
[DataRow("localhost")]
|
||||
[DataRow("test")]
|
||||
[DataRow("http")]
|
||||
[DataRow("https")]
|
||||
public void IsValidUrl_ReturnsFalse_WhenUrlDoesNotContainDot(string url)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("https://www.example.com")]
|
||||
[DataRow("http://test.org")]
|
||||
[DataRow("ftp://files.example.net")]
|
||||
[DataRow("file://localhost/path/to/file.txt")]
|
||||
[DataRow("https://subdomain.example.co.uk")]
|
||||
[DataRow("http://192.168.1.1")]
|
||||
[DataRow("https://example.com:8080/path")]
|
||||
public void IsValidUrl_ReturnsTrue_WhenUrlIsWellFormedAbsolute(string url)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("www.example.com")]
|
||||
[DataRow("example.org")]
|
||||
[DataRow("subdomain.test.net")]
|
||||
[DataRow("github.com/user/repo")]
|
||||
[DataRow("stackoverflow.com/questions/123")]
|
||||
[DataRow("192.168.1.1")]
|
||||
public void IsValidUrl_ReturnsTrue_WhenUrlIsValidWithoutProtocol(string url)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("not a url")]
|
||||
[DataRow("invalid..url")]
|
||||
[DataRow("http://")]
|
||||
[DataRow("https://")]
|
||||
[DataRow("://example.com")]
|
||||
[DataRow("ht tp://example.com")]
|
||||
public void IsValidUrl_ReturnsFalse_WhenUrlIsInvalid(string url)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(" https://www.example.com ")]
|
||||
[DataRow("\t\tgithub.com\t\t")]
|
||||
[DataRow(" \r\n stackoverflow.com \r\n ")]
|
||||
public void IsValidUrl_TrimsWhitespace_BeforeValidation(string url)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("tel:+1234567890")]
|
||||
[DataRow("javascript:alert('test')")]
|
||||
public void IsValidUrl_ReturnsFalse_ForNonWebProtocols(string url)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(null)]
|
||||
[DataRow("")]
|
||||
[DataRow(" ")]
|
||||
public void NormalizeUrl_ReturnsInput_WhenUrlIsNullOrWhitespace(string url)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.NormalizeUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(url, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("https://www.example.com")]
|
||||
[DataRow("http://test.org")]
|
||||
[DataRow("ftp://files.example.net")]
|
||||
[DataRow("file://localhost/path/to/file.txt")]
|
||||
public void NormalizeUrl_ReturnsUnchanged_WhenUrlIsAlreadyWellFormed(string url)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.NormalizeUrl(url);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(url, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("www.example.com", "https://www.example.com")]
|
||||
[DataRow("example.org", "https://example.org")]
|
||||
[DataRow("github.com/user/repo", "https://github.com/user/repo")]
|
||||
[DataRow("stackoverflow.com/questions/123", "https://stackoverflow.com/questions/123")]
|
||||
public void NormalizeUrl_AddsHttpsPrefix_WhenNoProtocolPresent(string input, string expected)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.NormalizeUrl(input);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(" www.example.com ", "https://www.example.com")]
|
||||
[DataRow("\t\tgithub.com\t\t", "https://github.com")]
|
||||
[DataRow(" \r\n stackoverflow.com \r\n ", "https://stackoverflow.com")]
|
||||
public void NormalizeUrl_TrimsWhitespace_BeforeNormalizing(string input, string expected)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.NormalizeUrl(input);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"C:\Users\Test\Documents\file.txt")]
|
||||
[DataRow(@"D:\Projects\MyProject\readme.md")]
|
||||
[DataRow(@"E:\")]
|
||||
[DataRow(@"F:")]
|
||||
[DataRow(@"G:\folder\subfolder")]
|
||||
public void IsValidUrl_ReturnsTrue_ForValidLocalPaths(string path)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(path);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"\\server\share")]
|
||||
[DataRow(@"\\server\share\folder")]
|
||||
[DataRow(@"\\192.168.1.100\public")]
|
||||
[DataRow(@"\\myserver\documents\file.docx")]
|
||||
[DataRow(@"\\domain.com\share\folder\file.pdf")]
|
||||
public void IsValidUrl_ReturnsTrue_ForValidNetworkPaths(string path)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(path);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"\\")]
|
||||
[DataRow(@":")]
|
||||
[DataRow(@"Z")]
|
||||
[DataRow(@"folder")]
|
||||
[DataRow(@"folder\file.txt")]
|
||||
[DataRow(@"documents\project\readme.md")]
|
||||
[DataRow(@"./config/settings.json")]
|
||||
[DataRow(@"../data/input.csv")]
|
||||
public void IsValidUrl_ReturnsFalse_ForInvalidPathsAndRelativePaths(string path)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.IsValidUrl(path);
|
||||
|
||||
// Assert
|
||||
Assert.IsFalse(result);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"C:\Users\Test\Documents\file.txt")]
|
||||
[DataRow(@"D:\Projects\MyProject")]
|
||||
[DataRow(@"E:\")]
|
||||
public void NormalizeUrl_ConvertsLocalPathToFileUri_WhenValidLocalPath(string path)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.NormalizeUrl(path);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.StartsWith("file:///", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.IsTrue(result.Contains(path.Replace('\\', '/')));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow(@"\\server\share")]
|
||||
[DataRow(@"\\192.168.1.100\public")]
|
||||
[DataRow(@"\\myserver\documents")]
|
||||
public void NormalizeUrl_ConvertsNetworkPathToFileUri_WhenValidNetworkPath(string path)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.NormalizeUrl(path);
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(result.StartsWith("file://", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.IsTrue(result.Contains(path.Replace('\\', '/')));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[DataRow("file:///C:/Users/Test/file.txt")]
|
||||
[DataRow("file://server/share/folder")]
|
||||
public void NormalizeUrl_ReturnsUnchanged_WhenAlreadyFileUri(string fileUri)
|
||||
{
|
||||
// Act
|
||||
var result = UrlHelper.NormalizeUrl(fileUri);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(fileUri, result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
// 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.IO;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.ClipboardHistory.Helpers;
|
||||
|
||||
internal static class UrlHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates if a string is a valid URL or file path
|
||||
/// </summary>
|
||||
/// <param name="url">The string to validate</param>
|
||||
/// <returns>True if the string is a valid URL or file path, false otherwise</returns>
|
||||
internal static bool IsValidUrl(string url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Trim whitespace for validation
|
||||
url = url.Trim();
|
||||
|
||||
// URLs should not contain newlines
|
||||
if (url.Contains('\n', StringComparison.Ordinal) || url.Contains('\r', StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's a valid file path (local or network)
|
||||
if (IsValidFilePath(url))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!url.Contains('.', StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// eg: 'com', 'org'. We don't think it's a valid url.
|
||||
// This can simplify the logic of checking if the url is valid.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
|
||||
!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase) &&
|
||||
!url.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) &&
|
||||
!url.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (Uri.IsWellFormedUriString("https://" + url, UriKind.Absolute))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalizes a URL or file path by adding appropriate schema if none is present
|
||||
/// </summary>
|
||||
/// <param name="url">The URL or file path to normalize</param>
|
||||
/// <returns>Normalized URL or file path with schema</returns>
|
||||
internal static string NormalizeUrl(string url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
{
|
||||
return url;
|
||||
}
|
||||
|
||||
// Trim whitespace
|
||||
url = url.Trim();
|
||||
|
||||
// If it's a valid file path, convert to file:// URI
|
||||
if (IsValidFilePath(url) && !url.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Convert to file URI (path is already absolute since we only accept absolute paths)
|
||||
return new Uri(url).ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// If conversion fails, return original
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
|
||||
{
|
||||
if (!url.StartsWith("http://", StringComparison.OrdinalIgnoreCase) &&
|
||||
!url.StartsWith("https://", StringComparison.OrdinalIgnoreCase) &&
|
||||
!url.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) &&
|
||||
!url.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
url = "https://" + url;
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a string represents a valid file path (local or network)
|
||||
/// </summary>
|
||||
/// <param name="path">The string to check</param>
|
||||
/// <returns>True if the string is a valid file path, false otherwise</returns>
|
||||
private static bool IsValidFilePath(string path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Check for UNC paths (network paths starting with \\)
|
||||
if (path.StartsWith(@"\\", StringComparison.Ordinal))
|
||||
{
|
||||
// Basic UNC path validation: \\server\share or \\server\share\path
|
||||
var parts = path.Substring(2).Split('\\', StringSplitOptions.RemoveEmptyEntries);
|
||||
return parts.Length >= 2; // At minimum: server and share
|
||||
}
|
||||
|
||||
// Check for drive letters (C:\ or C:)
|
||||
if (path.Length >= 2 && char.IsLetter(path[0]) && path[1] == ':')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CommandPalette.Extensions;
|
||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||
using Windows.System;
|
||||
@@ -16,4 +11,6 @@ namespace Microsoft.CmdPal.Ext.ClipboardHistory;
|
||||
internal static class KeyChords
|
||||
{
|
||||
internal static KeyChord DeleteEntry { get; } = KeyChordHelpers.FromModifiers(ctrl: true, shift: true, vkey: VirtualKey.Delete);
|
||||
|
||||
internal static KeyChord OpenUrl { get; } = KeyChordHelpers.FromModifiers(ctrl: true, vkey: VirtualKey.O);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ internal sealed partial class ClipboardListItem : ListItem
|
||||
private readonly CommandContextItem _deleteContextMenuItem;
|
||||
private readonly CommandContextItem? _pasteCommand;
|
||||
private readonly CommandContextItem? _copyCommand;
|
||||
private readonly CommandContextItem? _openUrlCommand;
|
||||
private readonly Lazy<Details> _lazyDetails;
|
||||
|
||||
public override IDetails? Details
|
||||
@@ -72,11 +73,26 @@ internal sealed partial class ClipboardListItem : ListItem
|
||||
|
||||
_pasteCommand = new CommandContextItem(new PasteCommand(_item, ClipboardFormat.Text, _settingsManager));
|
||||
_copyCommand = new CommandContextItem(new CopyCommand(_item, ClipboardFormat.Text));
|
||||
|
||||
// Check if the text content is a valid URL and add OpenUrl command
|
||||
if (UrlHelper.IsValidUrl(_item.Content ?? string.Empty))
|
||||
{
|
||||
var normalizedUrl = UrlHelper.NormalizeUrl(_item.Content ?? string.Empty);
|
||||
_openUrlCommand = new CommandContextItem(new OpenUrlCommand(normalizedUrl))
|
||||
{
|
||||
RequestedShortcut = KeyChords.OpenUrl,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
_openUrlCommand = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_pasteCommand = null;
|
||||
_copyCommand = null;
|
||||
_openUrlCommand = null;
|
||||
}
|
||||
|
||||
RefreshCommands();
|
||||
@@ -99,12 +115,7 @@ internal sealed partial class ClipboardListItem : ListItem
|
||||
{
|
||||
case PrimaryAction.Paste:
|
||||
Command = _pasteCommand?.Command;
|
||||
MoreCommands =
|
||||
[
|
||||
_copyCommand!,
|
||||
new Separator(),
|
||||
_deleteContextMenuItem,
|
||||
];
|
||||
MoreCommands = BuildMoreCommands(_copyCommand);
|
||||
|
||||
if (_item.IsText)
|
||||
{
|
||||
@@ -124,12 +135,7 @@ internal sealed partial class ClipboardListItem : ListItem
|
||||
case PrimaryAction.Copy:
|
||||
default:
|
||||
Command = _copyCommand?.Command;
|
||||
MoreCommands =
|
||||
[
|
||||
_pasteCommand!,
|
||||
new Separator(),
|
||||
_deleteContextMenuItem,
|
||||
];
|
||||
MoreCommands = BuildMoreCommands(_pasteCommand);
|
||||
|
||||
if (_item.IsText)
|
||||
{
|
||||
@@ -148,6 +154,26 @@ internal sealed partial class ClipboardListItem : ListItem
|
||||
}
|
||||
}
|
||||
|
||||
private IContextItem[] BuildMoreCommands(CommandContextItem? firstCommand)
|
||||
{
|
||||
var commands = new List<IContextItem>();
|
||||
|
||||
if (firstCommand != null)
|
||||
{
|
||||
commands.Add(firstCommand);
|
||||
}
|
||||
|
||||
if (_openUrlCommand != null)
|
||||
{
|
||||
commands.Add(_openUrlCommand);
|
||||
}
|
||||
|
||||
commands.Add(new Separator());
|
||||
commands.Add(_deleteContextMenuItem);
|
||||
|
||||
return commands.ToArray();
|
||||
}
|
||||
|
||||
private Details CreateDetails()
|
||||
{
|
||||
IDetailsElement[] metadata =
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// 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.ClipboardHistory.UnitTests")]
|
||||
@@ -183,4 +183,7 @@
|
||||
<data name="settings_primary_action_copy" xml:space="preserve">
|
||||
<value>Copy to Clipboard</value>
|
||||
</data>
|
||||
<data name="open_url_command_name" xml:space="preserve">
|
||||
<value>Open URL</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -159,15 +159,6 @@ namespace Microsoft.CommandPalette.Extensions.Toolkit.Properties {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Open.
|
||||
/// </summary>
|
||||
internal static string Page_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("Page_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Settings.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,44 +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.Diagnostics.CodeAnalysis;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Activation
|
||||
{
|
||||
// For more information on understanding and extending activation flow see
|
||||
// https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/activation.md
|
||||
internal abstract class ActivationHandler
|
||||
{
|
||||
public abstract bool CanHandle(object args);
|
||||
|
||||
public abstract Task HandleAsync(object args);
|
||||
}
|
||||
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "abstract T and abstract")]
|
||||
internal abstract class ActivationHandler<T> : ActivationHandler
|
||||
where T : class
|
||||
{
|
||||
public override async Task HandleAsync(object args)
|
||||
{
|
||||
await HandleInternalAsync(args as T).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public override bool CanHandle(object args)
|
||||
{
|
||||
// CanHandle checks the args is of type you have configured
|
||||
return args is T && CanHandleInternal(args as T);
|
||||
}
|
||||
|
||||
// Override this method to add the activation logic in your activation handler
|
||||
protected abstract Task HandleInternalAsync(T args);
|
||||
|
||||
// You can override this method to add extra validation on activation args
|
||||
// to determine if your ActivationHandler should handle this activation args
|
||||
protected virtual bool CanHandleInternal(T args)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +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.Threading.Tasks;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Services;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Activation
|
||||
{
|
||||
internal sealed class DefaultActivationHandler : ActivationHandler<IActivatedEventArgs>
|
||||
{
|
||||
private readonly Type navElement;
|
||||
|
||||
public DefaultActivationHandler(Type navElement)
|
||||
{
|
||||
this.navElement = navElement;
|
||||
}
|
||||
|
||||
protected override async Task HandleInternalAsync(IActivatedEventArgs args)
|
||||
{
|
||||
// When the navigation stack isn't restored, navigate to the first page and configure
|
||||
// the new page by passing required information in the navigation parameter
|
||||
object arguments = null;
|
||||
if (args is LaunchActivatedEventArgs launchArgs)
|
||||
{
|
||||
arguments = launchArgs.Arguments;
|
||||
}
|
||||
|
||||
NavigationService.Navigate(navElement, arguments);
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override bool CanHandleInternal(IActivatedEventArgs args)
|
||||
{
|
||||
// None of the ActivationHandlers has handled the app activation
|
||||
return NavigationService.Frame.Content == null && navElement != null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Activation;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Windows.ApplicationModel.Activation;
|
||||
|
||||
namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
{
|
||||
// For more information on understanding and extending activation flow see
|
||||
// https://github.com/Microsoft/WindowsTemplateStudio/blob/master/docs/activation.md
|
||||
internal sealed class ActivationService
|
||||
{
|
||||
private readonly App app;
|
||||
private readonly Type defaultNavItem;
|
||||
private Lazy<UIElement> shell;
|
||||
|
||||
private object lastActivationArgs;
|
||||
|
||||
public ActivationService(App app, Type defaultNavItem, Lazy<UIElement> shell = null)
|
||||
{
|
||||
this.app = app;
|
||||
this.shell = shell;
|
||||
this.defaultNavItem = defaultNavItem;
|
||||
}
|
||||
|
||||
public async Task ActivateAsync(object activationArgs)
|
||||
{
|
||||
if (IsInteractive(activationArgs))
|
||||
{
|
||||
// Initialize services that you need before app activation
|
||||
// take into account that the splash screen is shown while this code runs.
|
||||
await InitializeAsync().ConfigureAwait(false);
|
||||
|
||||
// Do not repeat app initialization when the Window already has content,
|
||||
// just ensure that the window is active
|
||||
if (Window.Current.Content == null)
|
||||
{
|
||||
// Create a Shell or Frame to act as the navigation context
|
||||
Window.Current.Content = shell?.Value ?? new Frame();
|
||||
}
|
||||
}
|
||||
|
||||
// Depending on activationArgs one of ActivationHandlers or DefaultActivationHandler
|
||||
// will navigate to the first page
|
||||
await HandleActivationAsync(activationArgs).ConfigureAwait(false);
|
||||
lastActivationArgs = activationArgs;
|
||||
|
||||
if (IsInteractive(activationArgs))
|
||||
{
|
||||
// Ensure the current window is active
|
||||
Window.Current.Activate();
|
||||
|
||||
// Tasks after activation
|
||||
await StartupAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task InitializeAsync()
|
||||
{
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task HandleActivationAsync(object activationArgs)
|
||||
{
|
||||
var activationHandler = GetActivationHandlers()
|
||||
.FirstOrDefault(h => h.CanHandle(activationArgs));
|
||||
|
||||
if (activationHandler != null)
|
||||
{
|
||||
await activationHandler.HandleAsync(activationArgs).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (IsInteractive(activationArgs))
|
||||
{
|
||||
var defaultHandler = new DefaultActivationHandler(defaultNavItem);
|
||||
if (defaultHandler.CanHandle(activationArgs))
|
||||
{
|
||||
await defaultHandler.HandleAsync(activationArgs).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task StartupAsync()
|
||||
{
|
||||
await Task.CompletedTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static IEnumerable<ActivationHandler> GetActivationHandlers()
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
private static bool IsInteractive(object args)
|
||||
{
|
||||
return args is IActivatedEventArgs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,12 +24,6 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
{
|
||||
get
|
||||
{
|
||||
if (frame == null)
|
||||
{
|
||||
frame = Window.Current.Content as Frame;
|
||||
RegisterFrameEvents();
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user