From 7ea052e2dec009f2e5449d6f5b033bad9f2a0c14 Mon Sep 17 00:00:00 2001 From: Saravanan Ganapathi Date: Tue, 16 Dec 2025 21:26:56 +0530 Subject: [PATCH 1/7] Feature: Copy option to right click menu for sidebar --- .../Sidebar/CopyItemFromSidebarAction.cs | 115 ++++++++++++++++++ src/Files.App/Helpers/PathNormalization.cs | 9 ++ .../UserControls/SidebarViewModel.cs | 6 +- 3 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs diff --git a/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs b/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs new file mode 100644 index 000000000000..48242bc58b22 --- /dev/null +++ b/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs @@ -0,0 +1,115 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; +using System.IO; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage; + +namespace Files.App.Actions +{ + [GeneratedRichCommand] + internal sealed partial class CopyItemFromSidebarAction : ObservableObject, IAction + { + private readonly IContentPageContext context; + private readonly ISidebarContext SidebarContext; + + public string Label + => Strings.Copy.GetLocalizedResource(); + + public string Description + => Strings.CopyItemDescription.GetLocalizedFormatResource(1); + + public RichGlyph Glyph + => new(themedIconStyle: "App.ThemedIcons.Copy"); + public bool IsExecutable + => GetIsExecutable(); + + public CopyItemFromSidebarAction() + { + context = Ioc.Default.GetRequiredService(); + SidebarContext = Ioc.Default.GetRequiredService(); + } + + public async Task ExecuteAsync(object? parameter = null) + { + if (SidebarContext.RightClickedItem is null) + return; + + var item = SidebarContext.RightClickedItem; + var itemPath = item.Path; + + if (string.IsNullOrEmpty(itemPath)) + return; + + try + { + var dataPackage = new DataPackage() { RequestedOperation = DataPackageOperation.Copy }; + IStorageItem? storageItem = null; + + var folderResult = await context.ShellPage?.ShellViewModel?.GetFolderFromPathAsync(itemPath)!; + if (folderResult) + storageItem = folderResult.Result; + + if (storageItem is null) + { + await CopyPathFallback(itemPath); + return; + } + + if (storageItem is SystemStorageFolder or SystemStorageFile) + { + var standardItems = await new[] { storageItem }.ToStandardStorageItemsAsync(); + if (standardItems.Any()) + storageItem = standardItems.First(); + } + + dataPackage.Properties.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName; + dataPackage.SetStorageItems(new[] { storageItem }, false); + + Clipboard.SetContent(dataPackage); + } + catch (Exception ex) + { + if ((FileSystemStatusCode)ex.HResult is FileSystemStatusCode.Unauthorized) + { + await CopyPathFallback(itemPath); + return; + } + + } + } + + private bool GetIsExecutable() + { + var item = SidebarContext.RightClickedItem; + + return SidebarContext.IsItemRightClicked + && item is not null + && item.MenuOptions.IsLocationItem + && !IsNonCopyableLocation(item); + } + + private async Task CopyPathFallback(string path) + { + try + { + await FileOperationsHelpers.SetClipboard(new[] { path }, DataPackageOperation.Copy); + } + catch (Exception ex) + { + App.Logger.LogWarning(ex, "Failed to copy path to clipboard."); + } + } + + private bool IsNonCopyableLocation(INavigationControlItem item) + { + if (string.IsNullOrEmpty(item.Path)) + return true; + + return string.Equals(item.Path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) || + string.Equals(item.Path, Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase) || + string.Equals(item.Path, Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/Files.App/Helpers/PathNormalization.cs b/src/Files.App/Helpers/PathNormalization.cs index b992063950c6..5ce03617e376 100644 --- a/src/Files.App/Helpers/PathNormalization.cs +++ b/src/Files.App/Helpers/PathNormalization.cs @@ -77,6 +77,15 @@ public static string Combine(string folder, string name) if (string.IsNullOrEmpty(folder)) return name; + // Handle case where name is a rooted path (e.g., "E:\") + if (Path.IsPathRooted(name)) + { + var root = Path.GetPathRoot(name); + if (!string.IsNullOrEmpty(root) && name.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) == root.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)) + // Just use the drive letter + name = root.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, ':'); + } + return folder.Contains('/', StringComparison.Ordinal) ? Path.Combine(folder, name).Replace("\\", "/", StringComparison.Ordinal) : Path.Combine(folder, name); } } diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 6c07f8ea1d20..55707aac6946 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -952,7 +952,7 @@ private List GetLocationItemMenuItems(INavigatio var isDriveItem = item is DriveItem; var isDriveItemPinned = isDriveItem && ((DriveItem)item).IsPinned; - + return new List() { new ContextMenuFlyoutItemViewModel() @@ -989,6 +989,10 @@ private List GetLocationItemMenuItems(INavigatio { IsVisible = UserSettingsService.GeneralSettingsService.ShowOpenInNewPane && Commands.OpenInNewPaneFromSidebar.IsExecutable }.Build(), + new ContextMenuFlyoutItemViewModelBuilder(Commands.CopyItemFromSidebar) + { + IsVisible = Commands.CopyItemFromSidebar.IsExecutable + }.Build(), new ContextMenuFlyoutItemViewModel() { Text = Strings.PinFolderToSidebar.GetLocalizedResource(), From 565563f4ef73d05aa0608191647c1afa5a438eef Mon Sep 17 00:00:00 2001 From: Saravanan Ganapathi Date: Tue, 16 Dec 2025 21:47:11 +0530 Subject: [PATCH 2/7] Feature: Copy option to right click menu for home screen widgets. --- .../FileSystem/CopyItemFromHomeAction.cs | 114 ++++++++++++++++++ .../Widgets/DrivesWidgetViewModel.cs | 4 + .../NetworkLocationsWidgetViewModel.cs | 4 + .../Widgets/QuickAccessWidgetViewModel.cs | 4 + 4 files changed, 126 insertions(+) create mode 100644 src/Files.App/Actions/FileSystem/CopyItemFromHomeAction.cs diff --git a/src/Files.App/Actions/FileSystem/CopyItemFromHomeAction.cs b/src/Files.App/Actions/FileSystem/CopyItemFromHomeAction.cs new file mode 100644 index 000000000000..e23a50b5c7ef --- /dev/null +++ b/src/Files.App/Actions/FileSystem/CopyItemFromHomeAction.cs @@ -0,0 +1,114 @@ +// Copyright (c) Files Community +// Licensed under the MIT License. + +using Microsoft.Extensions.Logging; +using System.IO; +using Windows.ApplicationModel.DataTransfer; +using Windows.Storage; + +namespace Files.App.Actions +{ + [GeneratedRichCommand] + internal sealed partial class CopyItemFromHomeAction : ObservableObject, IAction + { + private readonly IContentPageContext context; + private readonly IHomePageContext HomePageContext; + + public string Label + => Strings.Copy.GetLocalizedResource(); + + public string Description + => Strings.CopyItemDescription.GetLocalizedFormatResource(1); + + public RichGlyph Glyph + => new(themedIconStyle: "App.ThemedIcons.Copy"); + public bool IsExecutable + => GetIsExecutable(); + + public CopyItemFromHomeAction() + { + context = Ioc.Default.GetRequiredService(); + HomePageContext = Ioc.Default.GetRequiredService(); + } + + public async Task ExecuteAsync(object? parameter = null) + { + if (HomePageContext.RightClickedItem is null) + return; + + var item = HomePageContext.RightClickedItem; + var itemPath = item.Path; + + if (string.IsNullOrEmpty(itemPath)) + return; + + try + { + var dataPackage = new DataPackage() { RequestedOperation = DataPackageOperation.Copy }; + IStorageItem? storageItem = null; + + var folderResult = await context.ShellPage?.ShellViewModel?.GetFolderFromPathAsync(itemPath)!; + if (folderResult) + storageItem = folderResult.Result; + + if (storageItem is null) + { + await CopyPathFallback(itemPath); + return; + } + + if (storageItem is SystemStorageFolder or SystemStorageFile) + { + var standardItems = await new[] { storageItem }.ToStandardStorageItemsAsync(); + if (standardItems.Any()) + storageItem = standardItems.First(); + } + + dataPackage.Properties.PackageFamilyName = Windows.ApplicationModel.Package.Current.Id.FamilyName; + dataPackage.SetStorageItems(new[] { storageItem }, false); + + Clipboard.SetContent(dataPackage); + } + catch (Exception ex) + { + if ((FileSystemStatusCode)ex.HResult is FileSystemStatusCode.Unauthorized) + { + await CopyPathFallback(itemPath); + return; + } + + } + } + + private bool GetIsExecutable() + { + var item = HomePageContext.RightClickedItem; + + return HomePageContext.IsAnyItemRightClicked + && item is not null + && !IsNonCopyableLocation(item); + } + + private async Task CopyPathFallback(string path) + { + try + { + await FileOperationsHelpers.SetClipboard(new[] { path }, DataPackageOperation.Copy); + } + catch (Exception ex) + { + App.Logger.LogWarning(ex, "Failed to copy path to clipboard."); + } + } + + private bool IsNonCopyableLocation(WidgetCardItem item) + { + if (string.IsNullOrEmpty(item.Path)) + return true; + + return string.Equals(item.Path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) || + string.Equals(item.Path, Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase) || + string.Equals(item.Path, Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/Files.App/ViewModels/UserControls/Widgets/DrivesWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/DrivesWidgetViewModel.cs index f223f6c85d1f..7fd4fd069b9f 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/DrivesWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/DrivesWidgetViewModel.cs @@ -108,6 +108,10 @@ public override List GetItemMenuItems(WidgetCard { IsVisible = UserSettingsService.GeneralSettingsService.ShowOpenInNewPane && CommandManager.OpenInNewPaneFromHome.IsExecutable }.Build(), + new ContextMenuFlyoutItemViewModelBuilder(CommandManager.CopyItemFromHome) + { + IsVisible = CommandManager.CopyItemFromHome.IsExecutable + }.Build(), new() { Text = Strings.PinFolderToSidebar.GetLocalizedResource(), diff --git a/src/Files.App/ViewModels/UserControls/Widgets/NetworkLocationsWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/NetworkLocationsWidgetViewModel.cs index 639c0afd69f4..24bec80678c9 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/NetworkLocationsWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/NetworkLocationsWidgetViewModel.cs @@ -113,6 +113,10 @@ public override List GetItemMenuItems(WidgetCard { IsVisible = UserSettingsService.GeneralSettingsService.ShowOpenInNewPane && CommandManager.OpenInNewPaneFromHome.IsExecutable }.Build(), + new ContextMenuFlyoutItemViewModelBuilder(CommandManager.CopyItemFromHome) + { + IsVisible = CommandManager.CopyItemFromHome.IsExecutable + }.Build(), new() { Text = Strings.PinFolderToSidebar.GetLocalizedResource(), diff --git a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs index b56a8c62b318..7a69069391bb 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs @@ -104,6 +104,10 @@ public override List GetItemMenuItems(WidgetCard { IsVisible = UserSettingsService.GeneralSettingsService.ShowOpenInNewPane && CommandManager.OpenInNewPaneFromHome.IsExecutable }.Build(), + new ContextMenuFlyoutItemViewModelBuilder(CommandManager.CopyItemFromHome) + { + IsVisible = CommandManager.CopyItemFromHome.IsExecutable + }.Build(), new() { Text = Strings.PinFolderToSidebar.GetLocalizedResource(), From 0004c41ef98dc6424d7b5f47e65842b1ef0e8761 Mon Sep 17 00:00:00 2001 From: Saravanan Ganapathi Date: Tue, 16 Dec 2025 23:17:40 +0530 Subject: [PATCH 3/7] Fix: Primary commands (icon buttons) were not displaying in sidebar and home widget context menus. --- src/Files.App/ViewModels/UserControls/SidebarViewModel.cs | 7 +++++-- .../ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs | 7 +++++-- .../UserControls/Widgets/DrivesWidgetViewModel.cs | 1 + .../Widgets/NetworkLocationsWidgetViewModel.cs | 1 + .../UserControls/Widgets/QuickAccessWidgetViewModel.cs | 1 + 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 55707aac6946..254e736fac7b 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -736,13 +736,15 @@ public async void HandleItemContextInvokedAsync(object sender, ItemContextInvoke var itemContextMenuFlyout = new CommandBarFlyout() { - Placement = FlyoutPlacementMode.Full + AlwaysExpanded = true }; itemContextMenuFlyout.Opening += (sender, e) => App.LastOpenedFlyout = sender as CommandBarFlyout; var menuItems = GetLocationItemMenuItems(item, itemContextMenuFlyout); - var (_, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); + var (primaryElements, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); + + primaryElements.ForEach(itemContextMenuFlyout.PrimaryCommands.Add); secondaryElements .OfType() @@ -991,6 +993,7 @@ private List GetLocationItemMenuItems(INavigatio }.Build(), new ContextMenuFlyoutItemViewModelBuilder(Commands.CopyItemFromSidebar) { + IsPrimary = true, IsVisible = Commands.CopyItemFromSidebar.IsExecutable }.Build(), new ContextMenuFlyoutItemViewModel() diff --git a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs index f8d36f718095..0a0e864bc7d2 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs @@ -65,7 +65,7 @@ widgetCardItem.DataContext is not WidgetCardItem item || // Create a new Flyout var itemContextMenuFlyout = new CommandBarFlyout() { - Placement = FlyoutPlacementMode.Right + AlwaysExpanded = true }; // Hook events @@ -78,7 +78,10 @@ widgetCardItem.DataContext is not WidgetCardItem item || // Get items for the flyout var menuItems = GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path), fileTagsCardItem is not null && fileTagsCardItem.IsFolder); - var (_, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); + var (primaryElements, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); + + // Add menu items to the primary flyout + primaryElements.ForEach(itemContextMenuFlyout.PrimaryCommands.Add); // Set max width of the flyout secondaryElements diff --git a/src/Files.App/ViewModels/UserControls/Widgets/DrivesWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/DrivesWidgetViewModel.cs index 7fd4fd069b9f..9989ae4546aa 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/DrivesWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/DrivesWidgetViewModel.cs @@ -110,6 +110,7 @@ public override List GetItemMenuItems(WidgetCard }.Build(), new ContextMenuFlyoutItemViewModelBuilder(CommandManager.CopyItemFromHome) { + IsPrimary = true, IsVisible = CommandManager.CopyItemFromHome.IsExecutable }.Build(), new() diff --git a/src/Files.App/ViewModels/UserControls/Widgets/NetworkLocationsWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/NetworkLocationsWidgetViewModel.cs index 24bec80678c9..d4ffde53655a 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/NetworkLocationsWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/NetworkLocationsWidgetViewModel.cs @@ -115,6 +115,7 @@ public override List GetItemMenuItems(WidgetCard }.Build(), new ContextMenuFlyoutItemViewModelBuilder(CommandManager.CopyItemFromHome) { + IsPrimary = true, IsVisible = CommandManager.CopyItemFromHome.IsExecutable }.Build(), new() diff --git a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs index 7a69069391bb..1d28e2b413ee 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/QuickAccessWidgetViewModel.cs @@ -106,6 +106,7 @@ public override List GetItemMenuItems(WidgetCard }.Build(), new ContextMenuFlyoutItemViewModelBuilder(CommandManager.CopyItemFromHome) { + IsPrimary = true, IsVisible = CommandManager.CopyItemFromHome.IsExecutable }.Build(), new() From a35dc01a9b5049a8a595c8d2f5e85a623217c5d5 Mon Sep 17 00:00:00 2001 From: Saravanan Ganapathi Date: Wed, 17 Dec 2025 13:00:01 +0530 Subject: [PATCH 4/7] Fix: Disabled the copy option in the right-click menu for the Tags section and the Home item in the sidebar. --- src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs b/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs index 48242bc58b22..143af52780c5 100644 --- a/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs @@ -107,7 +107,9 @@ private bool IsNonCopyableLocation(INavigationControlItem item) if (string.IsNullOrEmpty(item.Path)) return true; - return string.Equals(item.Path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) || + return item.Path.StartsWith("tag:", StringComparison.OrdinalIgnoreCase) || + string.Equals(item.Path, "Home", StringComparison.OrdinalIgnoreCase) || + string.Equals(item.Path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) || string.Equals(item.Path, Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase) || string.Equals(item.Path, Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase); } From 83abb040e73cb1d7b83c67d13e053d20182c1fb6 Mon Sep 17 00:00:00 2001 From: Saravanan Ganapathi Date: Thu, 18 Dec 2025 14:42:53 +0530 Subject: [PATCH 5/7] Fix: Context menu not closing after clicking primary commands in sidebar and home widgets --- .../ViewModels/UserControls/SidebarViewModel.cs | 9 +++++++++ .../UserControls/Widgets/BaseWidgetViewModel.cs | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 254e736fac7b..3b5ebd7b8cc5 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -744,6 +744,15 @@ public async void HandleItemContextInvokedAsync(object sender, ItemContextInvoke var menuItems = GetLocationItemMenuItems(item, itemContextMenuFlyout); var (primaryElements, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); + // Workaround for WinUI (#5508) - AppBarButtons don't auto-close CommandBarFlyout + var closeHandler = new RoutedEventHandler((s, e) => itemContextMenuFlyout.Hide()); + primaryElements + .OfType() + .ForEach(button => button.Click += closeHandler); + primaryElements + .OfType() + .ForEach(button => button.Click += closeHandler); + primaryElements.ForEach(itemContextMenuFlyout.PrimaryCommands.Add); secondaryElements diff --git a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs index 0a0e864bc7d2..19aafc15d2df 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs @@ -80,6 +80,15 @@ widgetCardItem.DataContext is not WidgetCardItem item || var menuItems = GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path), fileTagsCardItem is not null && fileTagsCardItem.IsFolder); var (primaryElements, secondaryElements) = ContextFlyoutModelToElementHelper.GetAppBarItemsFromModel(menuItems); + // Workaround for WinUI (#5508) - AppBarButtons don't auto-close CommandBarFlyout + var closeHandler = new RoutedEventHandler((s, e) => itemContextMenuFlyout.Hide()); + primaryElements + .OfType() + .ForEach(button => button.Click += closeHandler); + primaryElements + .OfType() + .ForEach(button => button.Click += closeHandler); + // Add menu items to the primary flyout primaryElements.ForEach(itemContextMenuFlyout.PrimaryCommands.Add); From 1ae9656c63d112eaff4b6da8270f952bbf13fb6e Mon Sep 17 00:00:00 2001 From: Saravanan Ganapathi Date: Thu, 18 Dec 2025 15:04:38 +0530 Subject: [PATCH 6/7] Fix: Copy button showing for non-copyable locations in home widget context menus. --- .../Actions/FileSystem/CopyItemFromHomeAction.cs | 10 +++++++--- .../Actions/Sidebar/CopyItemFromSidebarAction.cs | 14 +++++++++----- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Files.App/Actions/FileSystem/CopyItemFromHomeAction.cs b/src/Files.App/Actions/FileSystem/CopyItemFromHomeAction.cs index e23a50b5c7ef..4eb342b9073f 100644 --- a/src/Files.App/Actions/FileSystem/CopyItemFromHomeAction.cs +++ b/src/Files.App/Actions/FileSystem/CopyItemFromHomeAction.cs @@ -106,9 +106,13 @@ private bool IsNonCopyableLocation(WidgetCardItem item) if (string.IsNullOrEmpty(item.Path)) return true; - return string.Equals(item.Path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.Path, Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.Path, Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase); + var normalizedPath = Constants.UserEnvironmentPaths.ShellPlaces.GetValueOrDefault( + item.Path.ToUpperInvariant(), + item.Path); + + return string.Equals(normalizedPath, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) || + string.Equals(normalizedPath, Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase) || + string.Equals(normalizedPath, Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase); } } } diff --git a/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs b/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs index 143af52780c5..0899f6269164 100644 --- a/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs +++ b/src/Files.App/Actions/Sidebar/CopyItemFromSidebarAction.cs @@ -107,11 +107,15 @@ private bool IsNonCopyableLocation(INavigationControlItem item) if (string.IsNullOrEmpty(item.Path)) return true; - return item.Path.StartsWith("tag:", StringComparison.OrdinalIgnoreCase) || - string.Equals(item.Path, "Home", StringComparison.OrdinalIgnoreCase) || - string.Equals(item.Path, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.Path, Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase) || - string.Equals(item.Path, Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase); + var normalizedPath = Constants.UserEnvironmentPaths.ShellPlaces.GetValueOrDefault( + item.Path.ToUpperInvariant(), + item.Path); + + return item.Path.StartsWith("tag:", StringComparison.OrdinalIgnoreCase) || + string.Equals(item.Path, "Home", StringComparison.OrdinalIgnoreCase) || + string.Equals(normalizedPath, Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.OrdinalIgnoreCase) || + string.Equals(normalizedPath, Constants.UserEnvironmentPaths.NetworkFolderPath, StringComparison.OrdinalIgnoreCase) || + string.Equals(normalizedPath, Constants.UserEnvironmentPaths.MyComputerPath, StringComparison.OrdinalIgnoreCase); } } } From d5eb2d58f84489f6d0b5ab61ffc4a4117478ff64 Mon Sep 17 00:00:00 2001 From: Saravanan Ganapathi Date: Thu, 18 Dec 2025 15:17:29 +0530 Subject: [PATCH 7/7] Fix: Revert the changes related to CommandBarFlyout Placement setting. --- src/Files.App/ViewModels/UserControls/SidebarViewModel.cs | 2 +- .../ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs index 3b5ebd7b8cc5..fc6df2219d73 100644 --- a/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/SidebarViewModel.cs @@ -736,7 +736,7 @@ public async void HandleItemContextInvokedAsync(object sender, ItemContextInvoke var itemContextMenuFlyout = new CommandBarFlyout() { - AlwaysExpanded = true + Placement = FlyoutPlacementMode.Right }; itemContextMenuFlyout.Opening += (sender, e) => App.LastOpenedFlyout = sender as CommandBarFlyout; diff --git a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs index 19aafc15d2df..173106ee7ac4 100644 --- a/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs +++ b/src/Files.App/ViewModels/UserControls/Widgets/BaseWidgetViewModel.cs @@ -65,7 +65,7 @@ widgetCardItem.DataContext is not WidgetCardItem item || // Create a new Flyout var itemContextMenuFlyout = new CommandBarFlyout() { - AlwaysExpanded = true + Placement = FlyoutPlacementMode.Right }; // Hook events