Skip to content

Commit 38c43a8

Browse files
authored
Feature: Improved shell extension loading (#11940)
1 parent 3e165e7 commit 38c43a8

File tree

8 files changed

+217
-78
lines changed

8 files changed

+217
-78
lines changed

src/Files.App/BaseLayout.cs

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
using Files.App.ViewModels;
1515
using Files.App.Views;
1616
using Files.Backend.Services.Settings;
17+
using Files.Shared;
1718
using Files.Shared.Enums;
1819
using Files.Shared.Extensions;
1920
using Microsoft.UI.Xaml;
@@ -611,7 +612,7 @@ public async void BaseContextFlyout_Opening(object? sender, object e)
611612
{
612613
var shellMenuItems = await ContextFlyoutItemHelper.GetItemContextShellCommandsAsync(workingDir: ParentShellPageInstance.FilesystemViewModel.WorkingDirectory, selectedItems: new List<ListedItem>(), shiftPressed: shiftPressed, showOpenMenu: false, shellContextMenuItemCancellationToken.Token);
613614
if (shellMenuItems.Any())
614-
AddShellItemsToMenu(shellMenuItems, BaseContextMenuFlyout, shiftPressed);
615+
await AddShellMenuItemsAsync(shellMenuItems, BaseContextMenuFlyout, shiftPressed);
615616
else
616617
RemoveOverflow(BaseContextMenuFlyout);
617618
}
@@ -670,7 +671,7 @@ private async Task LoadMenuItemsAsync()
670671
{
671672
var shellMenuItems = await ContextFlyoutItemHelper.GetItemContextShellCommandsAsync(workingDir: ParentShellPageInstance.FilesystemViewModel.WorkingDirectory, selectedItems: SelectedItems!, shiftPressed: shiftPressed, showOpenMenu: false, shellContextMenuItemCancellationToken.Token);
672673
if (shellMenuItems.Any())
673-
AddShellItemsToMenu(shellMenuItems, ItemContextMenuFlyout, shiftPressed);
674+
await AddShellMenuItemsAsync(shellMenuItems, ItemContextMenuFlyout, shiftPressed);
674675
else
675676
RemoveOverflow(ItemContextMenuFlyout);
676677
}
@@ -717,12 +718,13 @@ private void AddNewFileTagsToMenu(CommandBarFlyout contextMenu)
717718
});
718719
}
719720

720-
private void AddShellItemsToMenu(List<ContextMenuFlyoutItemViewModel> shellMenuItems, CommandBarFlyout contextMenuFlyout, bool shiftPressed)
721+
private async Task AddShellMenuItemsAsync(List<ContextMenuFlyoutItemViewModel> shellMenuItems, CommandBarFlyout contextMenuFlyout, bool shiftPressed)
721722
{
722-
var openWithSubItems = ItemModelListToContextFlyoutHelper.GetMenuFlyoutItemsFromModel(ShellContextmenuHelper.GetOpenWithItems(shellMenuItems));
723-
var sendToSubItems = ItemModelListToContextFlyoutHelper.GetMenuFlyoutItemsFromModel(ShellContextmenuHelper.GetSendToItems(shellMenuItems));
724-
var mainShellMenuItems = shellMenuItems.RemoveFrom(!UserSettingsService.PreferencesSettingsService.MoveShellExtensionsToSubMenu ? int.MaxValue : shiftPressed ? 6 : 0);
725-
var overflowShellMenuItemsUnfiltered = shellMenuItems.Except(mainShellMenuItems).ToList();
723+
var openWithMenuItem = shellMenuItems.FirstOrDefault(x => x.Tag is Win32ContextMenuItem { CommandString: "openas" });
724+
var sendToMenuItem = shellMenuItems.FirstOrDefault(x => x.Tag is Win32ContextMenuItem { CommandString: "sendto" });
725+
var shellMenuItemsFiltered = shellMenuItems.Where(x => x != openWithMenuItem && x != sendToMenuItem).ToList();
726+
var mainShellMenuItems = shellMenuItemsFiltered.RemoveFrom(!UserSettingsService.PreferencesSettingsService.MoveShellExtensionsToSubMenu ? int.MaxValue : shiftPressed ? 6 : 0);
727+
var overflowShellMenuItemsUnfiltered = shellMenuItemsFiltered.Except(mainShellMenuItems).ToList();
726728
var overflowShellMenuItems = overflowShellMenuItemsUnfiltered.Where(
727729
(x, i) => (x.ItemType == ItemType.Separator &&
728730
overflowShellMenuItemsUnfiltered[i + 1 < overflowShellMenuItemsUnfiltered.Count ? i + 1 : i].ItemType != ItemType.Separator)
@@ -794,38 +796,64 @@ private void AddShellItemsToMenu(List<ContextMenuFlyoutItemViewModel> shellMenuI
794796
var openWithOverflow = contextMenuFlyout.SecondaryCommands.FirstOrDefault(x => x is AppBarButton abb && (abb.Tag as string) == "OpenWithOverflow") as AppBarButton;
795797

796798
var openWith = contextMenuFlyout.SecondaryCommands.FirstOrDefault(x => x is AppBarButton abb && (abb.Tag as string) == "OpenWith") as AppBarButton;
797-
if (openWithSubItems is not null && openWithOverflow is not null && openWith is not null)
799+
if (openWithMenuItem?.LoadSubMenuAction is not null && openWithOverflow is not null && openWith is not null)
798800
{
799-
var flyout = (MenuFlyout)openWithOverflow.Flyout;
801+
await openWithMenuItem.LoadSubMenuAction.Invoke();
802+
var openWithSubItems = ItemModelListToContextFlyoutHelper.GetMenuFlyoutItemsFromModel(ShellContextmenuHelper.GetOpenWithItems(shellMenuItems));
800803

801-
flyout.Items.Clear();
804+
if (openWithSubItems is not null)
805+
{
806+
var flyout = (MenuFlyout)openWithOverflow.Flyout;
807+
808+
flyout.Items.Clear();
802809

803-
foreach (var item in openWithSubItems)
804-
flyout.Items.Add(item);
810+
foreach (var item in openWithSubItems)
811+
flyout.Items.Add(item);
805812

806-
openWithOverflow.Flyout = flyout;
807-
openWith.Visibility = Visibility.Collapsed;
808-
openWithOverflow.Visibility = Visibility.Visible;
813+
openWithOverflow.Flyout = flyout;
814+
openWith.Visibility = Visibility.Collapsed;
815+
openWithOverflow.Visibility = Visibility.Visible;
816+
}
809817
}
810818

811819
// Add items to sendto dropdown
812820
var sendToOverflow = contextMenuFlyout.SecondaryCommands.FirstOrDefault(x => x is AppBarButton abb && (abb.Tag as string) == "SendToOverflow") as AppBarButton;
813821

814822
var sendTo = contextMenuFlyout.SecondaryCommands.FirstOrDefault(x => x is AppBarButton abb && (abb.Tag as string) == "SendTo") as AppBarButton;
815-
if (sendToSubItems is not null && sendToOverflow is not null)
823+
if (sendToMenuItem?.LoadSubMenuAction is not null && sendToOverflow is not null && sendTo is not null)
816824
{
817-
var flyout = (MenuFlyout)sendToOverflow.Flyout;
825+
await sendToMenuItem.LoadSubMenuAction.Invoke();
826+
var sendToSubItems = ItemModelListToContextFlyoutHelper.GetMenuFlyoutItemsFromModel(ShellContextmenuHelper.GetSendToItems(shellMenuItems));
818827

819-
flyout.Items.Clear();
828+
if (sendToSubItems is not null)
829+
{
830+
var flyout = (MenuFlyout)sendToOverflow.Flyout;
831+
832+
flyout.Items.Clear();
820833

821-
foreach (var item in sendToSubItems)
822-
flyout.Items.Add(item);
834+
foreach (var item in sendToSubItems)
835+
flyout.Items.Add(item);
823836

824-
sendToOverflow.Flyout = flyout;
825-
sendTo.Visibility = Visibility.Collapsed;
826-
sendToOverflow.Visibility = Visibility.Visible;
837+
sendToOverflow.Flyout = flyout;
838+
sendTo.Visibility = Visibility.Collapsed;
839+
sendToOverflow.Visibility = Visibility.Visible;
840+
}
827841
}
828842

843+
// Add items to main shell submenu
844+
mainShellMenuItems.Where(x => x.LoadSubMenuAction is not null).ForEach(async x => {
845+
await x.LoadSubMenuAction.Invoke();
846+
847+
ShellContextmenuHelper.AddItemsToMainMenu(mainItems, x);
848+
});
849+
850+
// Add items to overflow shell submenu
851+
overflowShellMenuItems.Where(x => x.LoadSubMenuAction is not null).ForEach(async x => {
852+
await x.LoadSubMenuAction.Invoke();
853+
854+
ShellContextmenuHelper.AddItemsToOverflowMenu(overflowItem, x);
855+
});
856+
829857
if (itemsControl is not null)
830858
{
831859
itemsControl.Items.OfType<FrameworkElement>().ForEach(item =>

src/Files.App/Helpers/ItemModelListToContextFlyoutHelper.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,21 @@ public static class ItemModelListToContextFlyoutHelper
2626
var flyout = new List<MenuFlyoutItemBase>();
2727
items.ForEach(i =>
2828
{
29-
flyout.Add(GetMenuItem(i));
29+
var menuItem = GetMenuItem(i);
30+
flyout.Add(menuItem);
31+
if (menuItem is MenuFlyoutSubItem menuFlyoutSubItem && menuFlyoutSubItem.Items.Count == 0)
32+
{
33+
// Add a placeholder
34+
menuItem.Visibility = Visibility.Collapsed;
35+
36+
var placeolder = new MenuFlyoutItem()
37+
{
38+
Text = menuFlyoutSubItem.Text,
39+
Tag = menuFlyoutSubItem.Tag,
40+
Icon = menuFlyoutSubItem.Icon,
41+
};
42+
flyout.Add(placeolder);
43+
}
3044
});
3145
return flyout;
3246
}
@@ -59,7 +73,7 @@ public static List<ICommandBarElement> GetAppBarButtonsFromModelIgnorePrimary(Li
5973
return elements;
6074
}
6175

62-
private static MenuFlyoutItemBase GetMenuItem(ContextMenuFlyoutItemViewModel item)
76+
public static MenuFlyoutItemBase GetMenuItem(ContextMenuFlyoutItemViewModel item)
6377
{
6478
return item.ItemType switch
6579
{
@@ -70,14 +84,14 @@ private static MenuFlyoutItemBase GetMenuItem(ContextMenuFlyoutItemViewModel ite
7084

7185
private static MenuFlyoutItemBase GetMenuFlyoutItem(ContextMenuFlyoutItemViewModel item)
7286
{
73-
if (item.Items?.Count > 0)
87+
if (item.Items is not null)
7488
{
7589
var flyoutSubItem = new MenuFlyoutSubItem()
7690
{
7791
Text = item.Text,
7892
Tag = item.Tag,
7993
};
80-
item.Items?.ForEach(i =>
94+
item.Items.ForEach(i =>
8195
{
8296
flyoutSubItem.Items.Add(GetMenuItem(i));
8397
});

src/Files.App/Helpers/ShellContextMenuHelper.cs

Lines changed: 93 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ bool filterMenuItemsImpl(string menuItem) => !string.IsNullOrEmpty(menuItem)
7474
return menuItemsList;
7575
}
7676

77-
public static void LoadMenuFlyoutItem(IList<ContextMenuFlyoutItemViewModel> menuItemsListLocal,
77+
private static void LoadMenuFlyoutItem(IList<ContextMenuFlyoutItemViewModel> menuItemsListLocal,
7878
ContextMenu contextMenu,
7979
IEnumerable<Win32ContextMenuItem> menuFlyoutItems,
8080
CancellationToken cancellationToken,
@@ -136,7 +136,7 @@ public static void LoadMenuFlyoutItem(IList<ContextMenuFlyoutItemViewModel> menu
136136
};
137137
menuItemsListLocal.Insert(0, menuLayoutItem);
138138
}
139-
else if (!string.IsNullOrEmpty(menuFlyoutItem.Label) && menuFlyoutItem.SubItems.Where(x => x.Type != MenuItemType.MFT_SEPARATOR).Any())
139+
else if (!string.IsNullOrEmpty(menuFlyoutItem.Label) && menuFlyoutItem.SubItems is not null)
140140
{
141141
if (string.Equals(menuFlyoutItem.Label, Win32API.ExtractStringFromDLL("shell32.dll", 30312)))
142142
menuFlyoutItem.CommandString = "sendto";
@@ -145,9 +145,23 @@ public static void LoadMenuFlyoutItem(IList<ContextMenuFlyoutItemViewModel> menu
145145
{
146146
Text = menuFlyoutItem.Label.Replace("&", "", StringComparison.Ordinal),
147147
Tag = menuFlyoutItem,
148+
BitmapIcon = image,
148149
Items = new List<ContextMenuFlyoutItemViewModel>(),
149150
};
150-
LoadMenuFlyoutItem(menuLayoutSubItem.Items, contextMenu, menuFlyoutItem.SubItems, cancellationToken, showIcons);
151+
152+
if (menuFlyoutItem.SubItems.Any())
153+
{
154+
LoadMenuFlyoutItem(menuLayoutSubItem.Items, contextMenu, menuFlyoutItem.SubItems, cancellationToken, showIcons);
155+
}
156+
else
157+
{
158+
menuLayoutSubItem.LoadSubMenuAction = async () =>
159+
{
160+
if (await contextMenu.LoadSubMenu(menuFlyoutItem.SubItems))
161+
LoadMenuFlyoutItem(menuLayoutSubItem.Items, contextMenu, menuFlyoutItem.SubItems, cancellationToken, showIcons);
162+
};
163+
}
164+
151165
menuItemsListLocal.Insert(0, menuLayoutSubItem);
152166
}
153167
else if (!string.IsNullOrEmpty(menuFlyoutItem.Label))
@@ -243,37 +257,13 @@ public static async Task LoadShellMenuItems(
243257
showOpenMenu: false,
244258
default);
245259

246-
if (showOpenWithMenu)
247-
{
248-
var openWithItem = shellMenuItems.Where(x => (x.Tag as Win32ContextMenuItem)?.CommandString == "openas").ToList().FirstOrDefault();
249-
if (openWithItem is not null)
250-
{
251-
openWithItem.OpacityIcon = new OpacityIconModel()
252-
{
253-
OpacityIconStyle = "ColorIconOpenWith",
254-
};
255-
var (_, openWithItems) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(new List<ContextMenuFlyoutItemViewModel>() { openWithItem });
256-
var placeholder = itemContextMenuFlyout.SecondaryCommands.Where(x => Equals((x as AppBarButton)?.Tag, "OpenWithPlaceholder")).FirstOrDefault() as AppBarButton;
257-
if (placeholder is not null)
258-
placeholder.Visibility = Visibility.Collapsed;
259-
itemContextMenuFlyout.SecondaryCommands.Insert(0, openWithItems.FirstOrDefault());
260-
shellMenuItems.Remove(openWithItem);
261-
}
262-
}
260+
var openWithItem = showOpenWithMenu ? shellMenuItems.Where(x => (x.Tag as Win32ContextMenuItem)?.CommandString == "openas").ToList().FirstOrDefault() : null;
261+
if (openWithItem is not null)
262+
shellMenuItems.Remove(openWithItem);
263263

264-
if (showSendToMenu)
265-
{
266-
var sendToItem = shellMenuItems.Where(x => (x.Tag as Win32ContextMenuItem)?.CommandString == "sendto").ToList().FirstOrDefault();
267-
if (sendToItem is not null)
268-
{
269-
var (_, sendToItems) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(new List<ContextMenuFlyoutItemViewModel>() { sendToItem });
270-
var placeholder = itemContextMenuFlyout.SecondaryCommands.Where(x => Equals((x as AppBarButton)?.Tag, "SendToPlaceholder")).FirstOrDefault() as AppBarButton;
271-
if (placeholder is not null)
272-
placeholder.Visibility = Visibility.Collapsed;
273-
itemContextMenuFlyout.SecondaryCommands.Insert(1, sendToItems.FirstOrDefault());
274-
shellMenuItems.Remove(sendToItem);
275-
}
276-
}
264+
var sendToItem = showSendToMenu ? shellMenuItems.Where(x => (x.Tag as Win32ContextMenuItem)?.CommandString == "sendto").ToList().FirstOrDefault() : null;
265+
if (sendToItem is not null)
266+
shellMenuItems.Remove(sendToItem);
277267

278268
if (!UserSettingsService.PreferencesSettingsService.MoveShellExtensionsToSubMenu)
279269
{
@@ -317,8 +307,78 @@ public static async Task LoadShellMenuItems(
317307
overflowItem.Label = "ShowMoreOptions".GetLocalizedResource();
318308
overflowItem.IsEnabled = true;
319309
}
310+
311+
// Add items to openwith dropdown
312+
if (openWithItem is not null)
313+
{
314+
await openWithItem.LoadSubMenuAction.Invoke();
315+
316+
openWithItem.OpacityIcon = new OpacityIconModel()
317+
{
318+
OpacityIconStyle = "ColorIconOpenWith",
319+
};
320+
var (_, openWithItems) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(new List<ContextMenuFlyoutItemViewModel>() { openWithItem });
321+
var placeholder = itemContextMenuFlyout.SecondaryCommands.Where(x => Equals((x as AppBarButton)?.Tag, "OpenWithPlaceholder")).FirstOrDefault() as AppBarButton;
322+
if (placeholder is not null)
323+
placeholder.Visibility = Visibility.Collapsed;
324+
itemContextMenuFlyout.SecondaryCommands.Insert(0, openWithItems.FirstOrDefault());
325+
}
326+
327+
// Add items to sendto dropdown
328+
if (sendToItem is not null)
329+
{
330+
await sendToItem.LoadSubMenuAction.Invoke();
331+
332+
var (_, sendToItems) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(new List<ContextMenuFlyoutItemViewModel>() { sendToItem });
333+
var placeholder = itemContextMenuFlyout.SecondaryCommands.Where(x => Equals((x as AppBarButton)?.Tag, "SendToPlaceholder")).FirstOrDefault() as AppBarButton;
334+
if (placeholder is not null)
335+
placeholder.Visibility = Visibility.Collapsed;
336+
itemContextMenuFlyout.SecondaryCommands.Insert(1, sendToItems.FirstOrDefault());
337+
}
338+
339+
// Add items to shell submenu
340+
shellMenuItems.Where(x => x.LoadSubMenuAction is not null).ForEach(async x => {
341+
await x.LoadSubMenuAction.Invoke();
342+
343+
if (!UserSettingsService.PreferencesSettingsService.MoveShellExtensionsToSubMenu)
344+
{
345+
AddItemsToMainMenu(itemContextMenuFlyout.SecondaryCommands, x);
346+
}
347+
else if (itemContextMenuFlyout.SecondaryCommands.FirstOrDefault(x => x is AppBarButton appBarButton && (appBarButton.Tag as string) == "ItemOverflow") is AppBarButton overflowItem)
348+
{
349+
AddItemsToOverflowMenu(overflowItem, x);
350+
}
351+
});
320352
}
321353
catch { }
322354
}
355+
356+
public static void AddItemsToMainMenu(IEnumerable<ICommandBarElement> mainMenu, ContextMenuFlyoutItemViewModel viewModel)
357+
{
358+
var appBarButton = mainMenu.FirstOrDefault(x => (x as AppBarButton)?.Tag == viewModel.Tag) as AppBarButton;
359+
360+
if (appBarButton is not null)
361+
{
362+
var ctxFlyout = new MenuFlyout();
363+
ItemModelListToContextFlyoutHelper.GetMenuFlyoutItemsFromModel(viewModel.Items)?.ForEach(i => ctxFlyout.Items.Add(i));
364+
appBarButton.Flyout = ctxFlyout;
365+
appBarButton.Visibility = Visibility.Collapsed;
366+
appBarButton.Visibility = Visibility.Visible;
367+
}
368+
}
369+
370+
public static void AddItemsToOverflowMenu(AppBarButton? overflowItem, ContextMenuFlyoutItemViewModel viewModel)
371+
{
372+
if (overflowItem?.Flyout is MenuFlyout flyout)
373+
{
374+
var flyoutSubItem = flyout.Items.FirstOrDefault(x => x.Tag == viewModel.Tag) as MenuFlyoutSubItem;
375+
if (flyoutSubItem is not null)
376+
{
377+
viewModel.Items.ForEach(i => flyoutSubItem.Items.Add(ItemModelListToContextFlyoutHelper.GetMenuItem(i)));
378+
flyout.Items[flyout.Items.IndexOf(flyoutSubItem) + 1].Visibility = Visibility.Collapsed;
379+
flyoutSubItem.Visibility = Visibility.Visible;
380+
}
381+
}
382+
}
323383
}
324384
}

0 commit comments

Comments
 (0)