Skip to content

Commit 1024760

Browse files
authored
Code Quality: Added design and basic functionality for the upcoming Shelf feature (#16673)
1 parent 7ca7ffd commit 1024760

File tree

8 files changed

+244
-12
lines changed

8 files changed

+244
-12
lines changed

src/Files.App/Data/Contracts/ImagingService.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,12 @@ internal sealed class ImagingService : IImageService
1313
/// <inheritdoc/>
1414
public async Task<IImage?> GetIconAsync(IStorable storable, CancellationToken cancellationToken)
1515
{
16-
if (storable is not ILocatableStorable locatableStorable)
17-
return null;
18-
19-
var iconData = await FileThumbnailHelper.LoadIconFromPathAsync(locatableStorable.Path, 24u, ThumbnailMode.ListView, ThumbnailOptions.ResizeThumbnail);
16+
var iconData = await FileThumbnailHelper.GetIconAsync(storable.Id, Constants.ShellIconSizes.Small, storable is IFolder, IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale);
2017
if (iconData is null)
2118
return null;
2219

2320
var bitmapImage = await iconData.ToBitmapAsync();
24-
return new BitmapImageModel(bitmapImage);
21+
return bitmapImage is null ? null : new BitmapImageModel(bitmapImage);
2522
}
2623

2724
public async Task<IImage?> GetImageModelFromDataAsync(byte[] rawData)

src/Files.App/Data/Items/ShelfItem.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using Files.Shared.Utils;
2+
3+
namespace Files.App.Data.Items
4+
{
5+
[Bindable(true)]
6+
public sealed partial class ShelfItem : ObservableObject, IWrapper<IStorable>, IAsyncInitialize
7+
{
8+
private readonly IImageService _imageService;
9+
private readonly ICollection<ShelfItem> _sourceCollection;
10+
11+
[ObservableProperty] private IImage? _Icon;
12+
[ObservableProperty] private string? _Name;
13+
[ObservableProperty] private string? _Path;
14+
15+
/// <inheritdoc/>
16+
public IStorable Inner { get; }
17+
18+
public ShelfItem(IStorable storable, ICollection<ShelfItem> sourceCollection, IImage? icon = null)
19+
{
20+
_imageService = Ioc.Default.GetRequiredService<IImageService>();
21+
_sourceCollection = sourceCollection;
22+
Inner = storable;
23+
Icon = icon;
24+
Name = storable.Name;
25+
Path = storable.Id;
26+
}
27+
28+
/// <inheritdoc/>
29+
public async Task InitAsync(CancellationToken cancellationToken = default)
30+
{
31+
Icon = await _imageService.GetIconAsync(Inner, cancellationToken);
32+
}
33+
34+
[RelayCommand]
35+
private void Remove()
36+
{
37+
_sourceCollection.Remove(this);
38+
}
39+
}
40+
}

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4022,4 +4022,18 @@
40224022
<data name="ShowShelfPane" xml:space="preserve">
40234023
<value>Show Shelf Pane</value>
40244024
</data>
4025+
<data name="Shelf" xml:space="preserve">
4026+
<value>Shelf</value>
4027+
<comment>'Shelf' refers to the Shelf Pane feature, where users can conveniently drag and drop files for quick access and perform bulk actions with ease.</comment>
4028+
</data>
4029+
<data name="ClearItems" xml:space="preserve">
4030+
<value>Clear items</value>
4031+
</data>
4032+
<data name="RemoveFromShelf" xml:space="preserve">
4033+
<value>Remove from shelf</value>
4034+
</data>
4035+
<data name="AddToShelf" xml:space="preserve">
4036+
<value>Add to Shelf</value>
4037+
<comment>Tooltip that displays when dragging items to the Shelf Pane</comment>
4038+
</data>
40254039
</root>

src/Files.App/UserControls/Pane/ShelfPane.xaml

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,101 @@
33
x:Class="Files.App.UserControls.ShelfPane"
44
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
55
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
6-
xmlns:controls="using:Files.App.Controls"
76
xmlns:converters="using:Files.App.Converters"
87
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
98
xmlns:data="using:Files.App.Data.Items"
109
xmlns:helpers="using:Files.App.Helpers"
1110
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
12-
xmlns:usercontrols="using:Files.App.UserControls"
1311
mc:Ignorable="d">
1412

13+
<UserControl.Resources>
14+
<converters:ImageModelToImageConverter x:Key="ImageModelToImageConverter" />
15+
</UserControl.Resources>
16+
1517
<Grid
1618
Width="240"
19+
Padding="12"
20+
AllowDrop="True"
1721
Background="{ThemeResource App.Theme.InfoPane.BackgroundBrush}"
1822
BackgroundSizing="InnerBorderEdge"
1923
BorderBrush="{ThemeResource CardStrokeColorDefaultBrush}"
2024
BorderThickness="1"
21-
CornerRadius="8" />
25+
CornerRadius="8"
26+
DragOver="Shelf_DragOver"
27+
Drop="Shelf_Drop"
28+
RowSpacing="8">
29+
30+
<Grid.RowDefinitions>
31+
<RowDefinition Height="Auto" />
32+
<RowDefinition Height="*" />
33+
<RowDefinition Height="Auto" />
34+
</Grid.RowDefinitions>
35+
36+
<StackPanel Grid.Row="0" Spacing="8">
37+
38+
<!-- Title -->
39+
<TextBlock
40+
HorizontalAlignment="Center"
41+
Foreground="{ThemeResource TextFillColorTertiaryBrush}"
42+
Style="{StaticResource App.Theme.BodyTextBlockStyle}"
43+
Text="{helpers:ResourceString Name=Shelf}" />
44+
45+
<!-- (Divider) -->
46+
<Border Height="1" Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
47+
48+
</StackPanel>
49+
50+
<!-- Items List -->
51+
<ListView
52+
Grid.Row="1"
53+
DragItemsStarting="ListView_DragItemsStarting"
54+
ItemsSource="{x:Bind ItemsSource, Mode=OneWay}"
55+
ScrollViewer.VerticalScrollBarVisibility="Auto"
56+
ScrollViewer.VerticalScrollMode="Auto"
57+
SelectionMode="Extended">
58+
<ListView.ItemTemplate>
59+
<DataTemplate x:DataType="data:ShelfItem">
60+
<StackPanel Orientation="Horizontal" Spacing="8">
61+
<Image
62+
Width="16"
63+
Height="16"
64+
Source="{x:Bind Icon, Mode=OneWay, Converter={StaticResource ImageModelToImageConverter}}" />
65+
<TextBlock Text="{x:Bind Name, Mode=OneWay}" />
66+
67+
<StackPanel.ContextFlyout>
68+
<MenuFlyout>
69+
<MenuFlyoutItem Command="{x:Bind RemoveCommand}" Text="{helpers:ResourceString Name=RemoveFromShelf}">
70+
<MenuFlyoutItem.Icon>
71+
<FontIcon Glyph="&#xE738;" />
72+
</MenuFlyoutItem.Icon>
73+
</MenuFlyoutItem>
74+
</MenuFlyout>
75+
</StackPanel.ContextFlyout>
76+
</StackPanel>
77+
</DataTemplate>
78+
</ListView.ItemTemplate>
79+
80+
<ListView.ItemContainerStyle>
81+
<Style BasedOn="{StaticResource DefaultListViewItemStyle}" TargetType="ListViewItem">
82+
<Setter Property="Margin" Value="-4,0,-4,0" />
83+
<Setter Property="MinHeight" Value="36" />
84+
</Style>
85+
</ListView.ItemContainerStyle>
86+
</ListView>
87+
88+
89+
<StackPanel Grid.Row="2" Spacing="4">
90+
91+
<!-- (Divider) -->
92+
<Border Height="1" Background="{ThemeResource DividerStrokeColorDefaultBrush}" />
93+
94+
<!-- Bottom Actions -->
95+
<HyperlinkButton
96+
HorizontalAlignment="Center"
97+
VerticalAlignment="Center"
98+
Command="{x:Bind ClearCommand, Mode=OneWay}"
99+
Content="{helpers:ResourceString Name=ClearItems}" />
100+
101+
</StackPanel>
102+
</Grid>
22103
</UserControl>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,103 @@
11
// Copyright (c) Files Community
22
// Licensed under the MIT License.
33

4+
using Microsoft.UI.Xaml;
45
using Microsoft.UI.Xaml.Controls;
6+
using System.Runtime.InteropServices.ComTypes;
7+
using System.Windows.Input;
8+
using Vanara.PInvoke;
9+
using Windows.ApplicationModel.DataTransfer;
10+
using WinRT;
511

612
namespace Files.App.UserControls
713
{
814
public sealed partial class ShelfPane : UserControl
915
{
1016
public ShelfPane()
1117
{
18+
// TODO: [Shelf] Remove once view model is connected
19+
ItemsSource = new ObservableCollection<ShelfItem>();
20+
1221
InitializeComponent();
1322
}
23+
24+
private void Shelf_DragOver(object sender, DragEventArgs e)
25+
{
26+
if (!FilesystemHelpers.HasDraggedStorageItems(e.DataView))
27+
return;
28+
29+
e.Handled = true;
30+
e.DragUIOverride.Caption = Strings.AddToShelf.GetLocalizedResource();
31+
e.AcceptedOperation = DataPackageOperation.Link;
32+
}
33+
34+
private async void Shelf_Drop(object sender, DragEventArgs e)
35+
{
36+
if (ItemsSource is null)
37+
return;
38+
39+
// Get items
40+
var storageService = Ioc.Default.GetRequiredService<IStorageService>();
41+
var storageItems = (await FilesystemHelpers.GetDraggedStorageItems(e.DataView)).ToArray();
42+
43+
// Add to list
44+
foreach (var item in storageItems)
45+
{
46+
var storable = item switch
47+
{
48+
StorageFileWithPath => (IStorable?)await storageService.TryGetFileAsync(item.Path),
49+
StorageFolderWithPath => (IStorable?)await storageService.TryGetFolderAsync(item.Path),
50+
_ => null
51+
};
52+
53+
if (storable is null)
54+
continue;
55+
56+
var shelfItem = new ShelfItem(storable, ItemsSource);
57+
_ = shelfItem.InitAsync();
58+
59+
ItemsSource.Add(shelfItem);
60+
}
61+
}
62+
63+
private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
64+
{
65+
if (ItemsSource is null)
66+
return;
67+
68+
var shellItemList = SafetyExtensions.IgnoreExceptions(() => ItemsSource.Select(x => new Vanara.Windows.Shell.ShellItem(x.Inner.Id)).ToArray());
69+
if (shellItemList?[0].FileSystemPath is not null)
70+
{
71+
var iddo = shellItemList[0].Parent?.GetChildrenUIObjects<IDataObject>(HWND.NULL, shellItemList);
72+
if (iddo is null)
73+
return;
74+
75+
shellItemList.ForEach(x => x.Dispose());
76+
var dataObjectProvider = e.Data.As<Shell32.IDataObjectProvider>();
77+
dataObjectProvider.SetDataObject(iddo);
78+
}
79+
else
80+
{
81+
// Only support IStorageItem capable paths
82+
var storageItems = ItemsSource.Select(x => VirtualStorageItem.FromPath(x.Inner.Id));
83+
e.Data.SetStorageItems(storageItems, false);
84+
}
85+
}
86+
87+
public IList<ShelfItem>? ItemsSource
88+
{
89+
get => (IList<ShelfItem>?)GetValue(ItemsSourceProperty);
90+
set => SetValue(ItemsSourceProperty, value);
91+
}
92+
public static readonly DependencyProperty ItemsSourceProperty =
93+
DependencyProperty.Register(nameof(ItemsSource), typeof(IList<ShelfItem>), typeof(ShelfPane), new PropertyMetadata(null));
94+
95+
public ICommand? ClearCommand
96+
{
97+
get => (ICommand?)GetValue(ClearCommandProperty);
98+
set => SetValue(ClearCommandProperty, value);
99+
}
100+
public static readonly DependencyProperty ClearCommandProperty =
101+
DependencyProperty.Register(nameof(ClearCommand), typeof(ICommand), typeof(ShelfPane), new PropertyMetadata(null));
14102
}
15103
}

src/Files.App/Views/MainPage.xaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@
248248
ShowInfoText="{x:Bind SidebarAdaptiveViewModel.PaneHolder.ActivePaneOrColumn.InstanceViewModel.IsPageTypeNotHome, Mode=OneWay}"
249249
Visibility="{x:Bind SidebarAdaptiveViewModel.PaneHolder.ActivePaneOrColumn.InstanceViewModel.IsPageTypeNotHome, Mode=OneWay}" />
250250

251+
<!-- Shelf Pane -->
251252
<uc:ShelfPane
252253
x:Name="ShelfPane"
253254
Grid.Row="0"

src/Files.Shared/Utils/IAsyncInitialize.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
// Copyright (c) Files Community
2-
// Licensed under the MIT License.
3-
4-
using System.Threading;
1+
using System.Threading;
52
using System.Threading.Tasks;
63

74
namespace Files.Shared.Utils

src/Files.Shared/Utils/IWrapper.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace Files.Shared.Utils
2+
{
3+
/// <summary>
4+
/// Wraps and exposes <typeparamref name="T"/> implementation for access.
5+
/// </summary>
6+
/// <typeparam name="T">The wrapped type.</typeparam>
7+
public interface IWrapper<out T>
8+
{
9+
/// <summary>
10+
/// Gets the inner member wrapped by the implementation.
11+
/// </summary>
12+
T Inner { get; }
13+
}
14+
}

0 commit comments

Comments
 (0)