From 1024760cef6db5a83ed3adc00ba2bc653b0db072 Mon Sep 17 00:00:00 2001 From: d2dyno <53011783+d2dyno1@users.noreply.github.com> Date: Mon, 6 Jan 2025 03:50:22 +0100 Subject: [PATCH] Code Quality: Added design and basic functionality for the upcoming Shelf feature (#16673) --- .../Data/Contracts/ImagingService.cs | 7 +- src/Files.App/Data/Items/ShelfItem.cs | 40 +++++++++ src/Files.App/Strings/en-US/Resources.resw | 14 +++ .../UserControls/Pane/ShelfPane.xaml | 87 +++++++++++++++++- .../UserControls/Pane/ShelfPane.xaml.cs | 88 +++++++++++++++++++ src/Files.App/Views/MainPage.xaml | 1 + src/Files.Shared/Utils/IAsyncInitialize.cs | 5 +- src/Files.Shared/Utils/IWrapper.cs | 14 +++ 8 files changed, 244 insertions(+), 12 deletions(-) create mode 100644 src/Files.App/Data/Items/ShelfItem.cs create mode 100644 src/Files.Shared/Utils/IWrapper.cs diff --git a/src/Files.App/Data/Contracts/ImagingService.cs b/src/Files.App/Data/Contracts/ImagingService.cs index 390824ac9d93..30d3dcd0a427 100644 --- a/src/Files.App/Data/Contracts/ImagingService.cs +++ b/src/Files.App/Data/Contracts/ImagingService.cs @@ -13,15 +13,12 @@ internal sealed class ImagingService : IImageService /// public async Task GetIconAsync(IStorable storable, CancellationToken cancellationToken) { - if (storable is not ILocatableStorable locatableStorable) - return null; - - var iconData = await FileThumbnailHelper.LoadIconFromPathAsync(locatableStorable.Path, 24u, ThumbnailMode.ListView, ThumbnailOptions.ResizeThumbnail); + var iconData = await FileThumbnailHelper.GetIconAsync(storable.Id, Constants.ShellIconSizes.Small, storable is IFolder, IconOptions.ReturnIconOnly | IconOptions.UseCurrentScale); if (iconData is null) return null; var bitmapImage = await iconData.ToBitmapAsync(); - return new BitmapImageModel(bitmapImage); + return bitmapImage is null ? null : new BitmapImageModel(bitmapImage); } public async Task GetImageModelFromDataAsync(byte[] rawData) diff --git a/src/Files.App/Data/Items/ShelfItem.cs b/src/Files.App/Data/Items/ShelfItem.cs new file mode 100644 index 000000000000..7facfabce5b9 --- /dev/null +++ b/src/Files.App/Data/Items/ShelfItem.cs @@ -0,0 +1,40 @@ +using Files.Shared.Utils; + +namespace Files.App.Data.Items +{ + [Bindable(true)] + public sealed partial class ShelfItem : ObservableObject, IWrapper, IAsyncInitialize + { + private readonly IImageService _imageService; + private readonly ICollection _sourceCollection; + + [ObservableProperty] private IImage? _Icon; + [ObservableProperty] private string? _Name; + [ObservableProperty] private string? _Path; + + /// + public IStorable Inner { get; } + + public ShelfItem(IStorable storable, ICollection sourceCollection, IImage? icon = null) + { + _imageService = Ioc.Default.GetRequiredService(); + _sourceCollection = sourceCollection; + Inner = storable; + Icon = icon; + Name = storable.Name; + Path = storable.Id; + } + + /// + public async Task InitAsync(CancellationToken cancellationToken = default) + { + Icon = await _imageService.GetIconAsync(Inner, cancellationToken); + } + + [RelayCommand] + private void Remove() + { + _sourceCollection.Remove(this); + } + } +} diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 02dbe67ff39d..a219e91f76cc 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -4022,4 +4022,18 @@ Show Shelf Pane + + Shelf + 'Shelf' refers to the Shelf Pane feature, where users can conveniently drag and drop files for quick access and perform bulk actions with ease. + + + Clear items + + + Remove from shelf + + + Add to Shelf + Tooltip that displays when dragging items to the Shelf Pane + \ No newline at end of file diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml b/src/Files.App/UserControls/Pane/ShelfPane.xaml index 1804873e84d6..952b0ca4861b 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml @@ -3,20 +3,101 @@ x:Class="Files.App.UserControls.ShelfPane" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:controls="using:Files.App.Controls" xmlns:converters="using:Files.App.Converters" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:data="using:Files.App.Data.Items" xmlns:helpers="using:Files.App.Helpers" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:usercontrols="using:Files.App.UserControls" mc:Ignorable="d"> + + + + + CornerRadius="8" + DragOver="Shelf_DragOver" + Drop="Shelf_Drop" + RowSpacing="8"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs index 25946f599056..baf685530c70 100644 --- a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs +++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs @@ -1,7 +1,13 @@ // Copyright (c) Files Community // Licensed under the MIT License. +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using System.Runtime.InteropServices.ComTypes; +using System.Windows.Input; +using Vanara.PInvoke; +using Windows.ApplicationModel.DataTransfer; +using WinRT; namespace Files.App.UserControls { @@ -9,7 +15,89 @@ public sealed partial class ShelfPane : UserControl { public ShelfPane() { + // TODO: [Shelf] Remove once view model is connected + ItemsSource = new ObservableCollection(); + InitializeComponent(); } + + private void Shelf_DragOver(object sender, DragEventArgs e) + { + if (!FilesystemHelpers.HasDraggedStorageItems(e.DataView)) + return; + + e.Handled = true; + e.DragUIOverride.Caption = Strings.AddToShelf.GetLocalizedResource(); + e.AcceptedOperation = DataPackageOperation.Link; + } + + private async void Shelf_Drop(object sender, DragEventArgs e) + { + if (ItemsSource is null) + return; + + // Get items + var storageService = Ioc.Default.GetRequiredService(); + var storageItems = (await FilesystemHelpers.GetDraggedStorageItems(e.DataView)).ToArray(); + + // Add to list + foreach (var item in storageItems) + { + var storable = item switch + { + StorageFileWithPath => (IStorable?)await storageService.TryGetFileAsync(item.Path), + StorageFolderWithPath => (IStorable?)await storageService.TryGetFolderAsync(item.Path), + _ => null + }; + + if (storable is null) + continue; + + var shelfItem = new ShelfItem(storable, ItemsSource); + _ = shelfItem.InitAsync(); + + ItemsSource.Add(shelfItem); + } + } + + private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e) + { + if (ItemsSource is null) + return; + + var shellItemList = SafetyExtensions.IgnoreExceptions(() => ItemsSource.Select(x => new Vanara.Windows.Shell.ShellItem(x.Inner.Id)).ToArray()); + if (shellItemList?[0].FileSystemPath is not null) + { + var iddo = shellItemList[0].Parent?.GetChildrenUIObjects(HWND.NULL, shellItemList); + if (iddo is null) + return; + + shellItemList.ForEach(x => x.Dispose()); + var dataObjectProvider = e.Data.As(); + dataObjectProvider.SetDataObject(iddo); + } + else + { + // Only support IStorageItem capable paths + var storageItems = ItemsSource.Select(x => VirtualStorageItem.FromPath(x.Inner.Id)); + e.Data.SetStorageItems(storageItems, false); + } + } + + public IList? ItemsSource + { + get => (IList?)GetValue(ItemsSourceProperty); + set => SetValue(ItemsSourceProperty, value); + } + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register(nameof(ItemsSource), typeof(IList), typeof(ShelfPane), new PropertyMetadata(null)); + + public ICommand? ClearCommand + { + get => (ICommand?)GetValue(ClearCommandProperty); + set => SetValue(ClearCommandProperty, value); + } + public static readonly DependencyProperty ClearCommandProperty = + DependencyProperty.Register(nameof(ClearCommand), typeof(ICommand), typeof(ShelfPane), new PropertyMetadata(null)); } } diff --git a/src/Files.App/Views/MainPage.xaml b/src/Files.App/Views/MainPage.xaml index 0c9da874c568..74aec3c6d77c 100644 --- a/src/Files.App/Views/MainPage.xaml +++ b/src/Files.App/Views/MainPage.xaml @@ -248,6 +248,7 @@ ShowInfoText="{x:Bind SidebarAdaptiveViewModel.PaneHolder.ActivePaneOrColumn.InstanceViewModel.IsPageTypeNotHome, Mode=OneWay}" Visibility="{x:Bind SidebarAdaptiveViewModel.PaneHolder.ActivePaneOrColumn.InstanceViewModel.IsPageTypeNotHome, Mode=OneWay}" /> + + /// Wraps and exposes implementation for access. + /// + /// The wrapped type. + public interface IWrapper + { + /// + /// Gets the inner member wrapped by the implementation. + /// + T Inner { get; } + } +}