Skip to content

Commit b26e831

Browse files
Feature: Added support for opening all tagged items from the Tags widget (#12972)
1 parent 81df0a6 commit b26e831

File tree

11 files changed

+219
-35
lines changed

11 files changed

+219
-35
lines changed
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) 2023 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
namespace Files.App.Actions
5+
{
6+
sealed class OpenAllTaggedActions: ObservableObject, IAction
7+
{
8+
private readonly IContentPageContext _pageContext;
9+
10+
private readonly ITagsContext _tagsContext;
11+
12+
public string Label
13+
=> "OpenAllTaggedItems".GetLocalizedResource();
14+
15+
public string Description
16+
=> "OpenAllTaggedItemsDescription".GetLocalizedResource();
17+
18+
public RichGlyph Glyph
19+
=> new("\uE71D");
20+
21+
public bool IsExecutable =>
22+
_pageContext.ShellPage is not null &&
23+
_tagsContext.TaggedItems.Any();
24+
25+
public OpenAllTaggedActions()
26+
{
27+
_pageContext = Ioc.Default.GetRequiredService<IContentPageContext>();
28+
_tagsContext = Ioc.Default.GetRequiredService<ITagsContext>();
29+
30+
_pageContext.PropertyChanged += Context_PropertyChanged;
31+
_tagsContext.PropertyChanged += Context_PropertyChanged;
32+
}
33+
34+
public async Task ExecuteAsync()
35+
{
36+
var files = _tagsContext.TaggedItems.Where(item => !item.IsFolder);
37+
var folders = _tagsContext.TaggedItems.Where(item => item.IsFolder);
38+
39+
await Task.WhenAll(files.Select(file
40+
=> NavigationHelpers.OpenPath(file.Path, _pageContext.ShellPage!)));
41+
42+
folders.ForEach(async folder => await NavigationHelpers.OpenPathInNewTab(folder.Path));
43+
}
44+
45+
private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e)
46+
{
47+
switch (e.PropertyName)
48+
{
49+
case nameof(IContentPageContext.ShellPage):
50+
case nameof(ITagsContext.TaggedItems):
51+
OnPropertyChanged(nameof(IsExecutable));
52+
break;
53+
}
54+
}
55+
}
56+
}

src/Files.App/App.xaml.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ private IHost ConfigureHost()
116116
.AddSingleton<IDisplayPageContext, DisplayPageContext>()
117117
.AddSingleton<IWindowContext, WindowContext>()
118118
.AddSingleton<IMultitaskingContext, MultitaskingContext>()
119+
.AddSingleton<ITagsContext, TagsContext>()
119120
.AddSingleton<IDialogService, DialogService>()
120121
.AddSingleton<IImageService, ImagingService>()
121122
.AddSingleton<IThreadingService, ThreadingService>()

src/Files.App/Data/Commands/CommandCodes.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,5 +190,8 @@ public enum CommandCodes
190190
GitPull,
191191
GitPush,
192192
GitSync,
193+
194+
// Tags
195+
OpenAllTaggedItems,
193196
}
194197
}

src/Files.App/Data/Commands/Manager/CommandManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ public IRichCommand this[HotKey hotKey]
166166
public IRichCommand GitPull => commands[CommandCodes.GitPull];
167167
public IRichCommand GitPush => commands[CommandCodes.GitPush];
168168
public IRichCommand GitSync => commands[CommandCodes.GitSync];
169+
public IRichCommand OpenAllTaggedItems => commands[CommandCodes.OpenAllTaggedItems];
169170

170171
public CommandManager()
171172
{
@@ -326,6 +327,7 @@ public CommandManager()
326327
[CommandCodes.GitPull] = new GitPullAction(),
327328
[CommandCodes.GitPush] = new GitPushAction(),
328329
[CommandCodes.GitSync] = new GitSyncAction(),
330+
[CommandCodes.OpenAllTaggedItems] = new OpenAllTaggedActions(),
329331
};
330332

331333
private void UpdateHotKeys()

src/Files.App/Data/Commands/Manager/ICommandManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,5 +169,7 @@ public interface ICommandManager : IEnumerable<IRichCommand>
169169
IRichCommand GitPull { get; }
170170
IRichCommand GitPush { get; }
171171
IRichCommand GitSync { get; }
172+
173+
IRichCommand OpenAllTaggedItems { get; }
172174
}
173175
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Copyright (c) 2023 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using Files.App.ViewModels.Widgets;
5+
6+
namespace Files.App.Data.Contexts
7+
{
8+
interface ITagsContext: INotifyPropertyChanged
9+
{
10+
IEnumerable<FileTagsItemViewModel> TaggedItems { get; }
11+
}
12+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) 2023 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using Files.App.ViewModels.Widgets;
5+
using System.Collections.Immutable;
6+
7+
namespace Files.App.Data.Contexts
8+
{
9+
sealed class TagsContext : ITagsContext
10+
{
11+
private static readonly IReadOnlyList<FileTagsItemViewModel> _emptyTaggedItemsList
12+
= Enumerable.Empty<FileTagsItemViewModel>().ToImmutableList();
13+
14+
public event PropertyChangedEventHandler? PropertyChanged;
15+
16+
private IEnumerable<FileTagsItemViewModel> _TaggedItems = _emptyTaggedItemsList;
17+
public IEnumerable<FileTagsItemViewModel> TaggedItems
18+
{
19+
get => _TaggedItems;
20+
set
21+
{
22+
_TaggedItems = value is not null
23+
? value
24+
: _emptyTaggedItemsList;
25+
26+
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TaggedItems)));
27+
}
28+
}
29+
30+
public TagsContext()
31+
{
32+
FileTagsContainerViewModel.SelectedTagsChanged += FileTagsContainerViewModel_SelectedTagsChanged;
33+
}
34+
35+
private void FileTagsContainerViewModel_SelectedTagsChanged(object sender, IEnumerable<FileTagsItemViewModel> items)
36+
{
37+
TaggedItems = items;
38+
}
39+
}
40+
}

src/Files.App/Strings/en-US/Resources.resw

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3398,4 +3398,10 @@
33983398
<data name="OpenInNewWindowDescription" xml:space="preserve">
33993399
<value>Open directory in new window</value>
34003400
</data>
3401+
<data name="OpenAllTaggedItems" xml:space="preserve">
3402+
<value>Open all</value>
3403+
</data>
3404+
<data name="OpenAllTaggedItemsDescription" xml:space="preserve">
3405+
<value>Open all tagged items</value>
3406+
</data>
34013407
</root>

src/Files.App/UserControls/Widgets/FileTagsWidget.xaml

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
99
xmlns:helpers="using:Files.App.Helpers"
1010
xmlns:local="using:Files.App.UserControls.Widgets"
11+
xmlns:localcontrols="using:Files.App.UserControls"
1112
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
1213
xmlns:vm="using:Files.App.ViewModels.Widgets"
1314
d:DesignHeight="300"
@@ -51,41 +52,63 @@
5152
</Grid.RowDefinitions>
5253

5354
<!-- Title -->
54-
<Grid BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}" BorderThickness="0,0,0,1">
55+
<Grid
56+
Background="Transparent"
57+
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
58+
BorderThickness="0,0,0,1">
5559
<Grid.ColumnDefinitions>
5660
<ColumnDefinition Width="Auto" />
5761
<ColumnDefinition Width="*" />
5862
<ColumnDefinition Width="Auto" />
5963
</Grid.ColumnDefinitions>
6064

61-
<StackPanel
62-
Padding="12,8,12,8"
63-
Orientation="Horizontal"
64-
Spacing="8">
65-
<!-- Tag Color -->
66-
<PathIcon Data="{StaticResource ColorIconFilledTag}" Foreground="{x:Bind Color, Mode=OneWay, Converter={StaticResource StringToBrushConverter}}" />
67-
68-
<!-- Tag Name -->
69-
<TextBlock
70-
Margin="0,-2,0,0"
71-
FontWeight="SemiBold"
72-
Text="{x:Bind Name, Mode=OneWay}" />
73-
</StackPanel>
74-
7565
<!-- View More -->
7666
<HyperlinkButton
77-
Grid.Column="2"
7867
Margin="4"
7968
AutomationProperties.Name="{helpers:ResourceString Name=ViewMore}"
8069
Command="{x:Bind ViewMoreCommand}"
8170
ToolTipService.ToolTip="{helpers:ResourceString Name=ViewMore}">
82-
<HyperlinkButton.Content>
83-
<FontIcon
84-
FontSize="12"
71+
<StackPanel Orientation="Horizontal" Spacing="8">
72+
<!-- Tag Color -->
73+
<PathIcon Data="{StaticResource ColorIconFilledTag}" Foreground="{x:Bind Color, Mode=OneWay, Converter={StaticResource StringToBrushConverter}}" />
74+
75+
<!-- Tag Name -->
76+
<TextBlock
77+
Margin="0,-2,0,0"
8578
FontWeight="SemiBold"
86-
Glyph="&#xE76C;" />
87-
</HyperlinkButton.Content>
79+
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
80+
Text="{x:Bind Name, Mode=OneWay}" />
81+
</StackPanel>
8882
</HyperlinkButton>
83+
84+
<!-- Additional Actions -->
85+
<Button
86+
Grid.Column="2"
87+
Margin="4"
88+
Background="Transparent"
89+
BorderBrush="Transparent"
90+
ToolTipService.ToolTip="{helpers:ResourceString Name=MoreOptions}">
91+
<Button.Flyout>
92+
<MenuFlyout>
93+
<MenuFlyoutItem
94+
x:Name="OpenAllItemsButton"
95+
AutomationProperties.Name="{helpers:ResourceString Name=OpenAllTaggedItems}"
96+
Command="{x:Bind OpenAllCommand}"
97+
Text="{helpers:ResourceString Name=OpenAllTaggedItems}"
98+
ToolTipService.ToolTip="{helpers:ResourceString Name=OpenAllTaggedItems}">
99+
<MenuFlyoutItem.Icon>
100+
<FontIcon Glyph="&#xE8A7;" />
101+
</MenuFlyoutItem.Icon>
102+
</MenuFlyoutItem>
103+
</MenuFlyout>
104+
</Button.Flyout>
105+
106+
<FontIcon
107+
FontSize="12"
108+
FontWeight="SemiBold"
109+
Foreground="{ThemeResource TextFillColorPrimaryBrush}"
110+
Glyph="&#xE712;" />
111+
</Button>
89112
</Grid>
90113

91114
<!-- Contents -->

src/Files.App/UserControls/Widgets/FileTagsWidget.xaml.cs

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@ namespace Files.App.UserControls.Widgets
1717
{
1818
public sealed partial class FileTagsWidget : HomePageWidget, IWidgetItemModel
1919
{
20+
private readonly IUserSettingsService userSettingsService;
21+
2022
public FileTagsWidgetViewModel ViewModel
2123
{
2224
get => (FileTagsWidgetViewModel)DataContext;
2325
set => DataContext = value;
2426
}
2527

26-
private readonly IUserSettingsService userSettingsService = Ioc.Default.GetRequiredService<IUserSettingsService>();
27-
2828
public IShellPage AppInstance;
29+
2930
public Func<string, Task>? OpenAction { get; set; }
3031

3132
public delegate void FileTagsOpenLocationInvokedEventHandler(object sender, PathNavigationEventArgs e);
@@ -50,6 +51,8 @@ public FileTagsWidgetViewModel ViewModel
5051

5152
public FileTagsWidget()
5253
{
54+
userSettingsService = Ioc.Default.GetRequiredService<IUserSettingsService>();
55+
5356
InitializeComponent();
5457

5558
// Second function is layered on top to ensure that OpenPath function is late initialized and a null reference is not passed-in
@@ -67,13 +70,13 @@ public FileTagsWidget()
6770
private void OpenProperties(WidgetCardItem? item)
6871
{
6972
EventHandler<object> flyoutClosed = null!;
70-
flyoutClosed = async (s, e) =>
73+
flyoutClosed = (s, e) =>
7174
{
7275
ItemContextMenuFlyout.Closed -= flyoutClosed;
7376
ListedItem listedItem = new(null!)
7477
{
75-
ItemPath = (item.Item as FileTagsItemViewModel).Path,
76-
ItemNameRaw = (item.Item as FileTagsItemViewModel).Name,
78+
ItemPath = (item.Item as FileTagsItemViewModel)?.Path ?? string.Empty,
79+
ItemNameRaw = (item.Item as FileTagsItemViewModel)?.Name ?? string.Empty,
7780
PrimaryItemAttribute = StorageItemTypes.Folder,
7881
ItemType = "Folder".GetLocalizedResource(),
7982
};
@@ -96,14 +99,30 @@ private async void FileTagItem_ItemClick(object sender, ItemClickEventArgs e)
9699
await itemViewModel.ClickCommand.ExecuteAsync(null);
97100
}
98101

99-
private async void AdaptiveGridView_RightTapped(object sender, RightTappedRoutedEventArgs e)
102+
private void AdaptiveGridView_RightTapped(object sender, RightTappedRoutedEventArgs e)
103+
{
104+
if (e.OriginalSource is not FrameworkElement element ||
105+
element.DataContext is not FileTagsItemViewModel item)
106+
{
107+
return;
108+
}
109+
110+
LoadContextMenu(
111+
element,
112+
e,
113+
GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path), item.IsFolder),
114+
rightClickedItem: item);
115+
}
116+
117+
private async void LoadContextMenu(
118+
FrameworkElement element,
119+
RightTappedRoutedEventArgs e,
120+
List<ContextMenuFlyoutItemViewModel> menuItems,
121+
FileTagsItemViewModel? rightClickedItem = null)
100122
{
101123
var itemContextMenuFlyout = new CommandBarFlyout { Placement = FlyoutPlacementMode.Full };
102124
itemContextMenuFlyout.Opening += (sender, e) => App.LastOpenedFlyout = sender as CommandBarFlyout;
103-
if (e.OriginalSource is not FrameworkElement element || element.DataContext is not FileTagsItemViewModel item)
104-
return;
105125

106-
var menuItems = GetItemMenuItems(item, QuickAccessService.IsItemPinned(item.Path), item.IsFolder);
107126
var (_, secondaryElements) = ItemModelListToContextFlyoutHelper.GetAppBarItemsFromModel(menuItems);
108127

109128
if (!UserSettingsService.GeneralSettingsService.MoveShellExtensionsToSubMenu)
@@ -113,8 +132,9 @@ private async void AdaptiveGridView_RightTapped(object sender, RightTappedRouted
113132
secondaryElements.ForEach(i => itemContextMenuFlyout.SecondaryCommands.Add(i));
114133
ItemContextMenuFlyout = itemContextMenuFlyout;
115134
itemContextMenuFlyout.ShowAt(element, new FlyoutShowOptions { Position = e.GetPosition(element) });
135+
if (rightClickedItem is not null)
136+
await ShellContextmenuHelper.LoadShellMenuItems(rightClickedItem.Path, itemContextMenuFlyout, showOpenWithMenu: true, showSendToMenu: true);
116137

117-
await ShellContextmenuHelper.LoadShellMenuItems(item.Path, itemContextMenuFlyout, showOpenWithMenu: true, showSendToMenu: true);
118138
e.Handled = true;
119139
}
120140

0 commit comments

Comments
 (0)