From 31aa589f82294989469ba116c37afb7fbe6d48f3 Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Thu, 23 Jan 2025 00:34:43 +0100
Subject: [PATCH 01/10] Begin work on dropping items

---
 .../UserControls/Pane/ShelfPane.xaml          |  2 +
 .../UserControls/Pane/ShelfPane.xaml.cs       | 46 +++++++++++--------
 src/Files.App/Views/Layouts/BaseLayoutPage.cs | 25 ++++++----
 3 files changed, 44 insertions(+), 29 deletions(-)

diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml b/src/Files.App/UserControls/Pane/ShelfPane.xaml
index de9399576fb9..987dedee0c29 100644
--- a/src/Files.App/UserControls/Pane/ShelfPane.xaml
+++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml
@@ -50,8 +50,10 @@
 
 		<!--  Items List  -->
 		<ListView
+			x:Name="ShelfItemsList"
 			Grid.Row="1"
 			Padding="8,4,8,4"
+			CanDragItems="True"
 			DragItemsStarting="ListView_DragItemsStarting"
 			ItemContainerTransitions="{x:Null}"
 			ItemsSource="{x:Bind ItemsSource, Mode=OneWay}"
diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
index baf685530c70..2bcc2a71a6a4 100644
--- a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
+++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
@@ -1,13 +1,16 @@
 // Copyright (c) Files Community
 // Licensed under the MIT License.
 
+using System.Runtime.InteropServices;
 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 Vanara.Windows.Shell;
 using WinRT;
+using DragEventArgs = Microsoft.UI.Xaml.DragEventArgs;
 
 namespace Files.App.UserControls
 {
@@ -43,6 +46,10 @@ private async void Shelf_Drop(object sender, DragEventArgs e)
 			// Add to list
 			foreach (var item in storageItems)
 			{
+				// Avoid adding duplicates
+				if (ItemsSource.Any(x => x.Inner.Id == item.Path))
+					continue;
+
 				var storable = item switch
 				{
 					StorageFileWithPath => (IStorable?)await storageService.TryGetFileAsync(item.Path),
@@ -60,28 +67,27 @@ private async void Shelf_Drop(object sender, DragEventArgs e)
 			}
 		}
 
-		private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
-		{
-			if (ItemsSource is null)
-				return;
+        private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
+        {
+            var apidl = SafetyExtensions.IgnoreExceptions(() => e.Items
+                .Cast<ShelfItem>()
+                .Select(x => new ShellItem(x.Inner.Id).PIDL)
+                .ToArray());
 
-			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<IDataObject>(HWND.NULL, shellItemList);
-				if (iddo is null)
-					return;
+            if (apidl is null)
+                return;
 
-				shellItemList.ForEach(x => x.Dispose());
-				var dataObjectProvider = e.Data.As<Shell32.IDataObjectProvider>();
-				dataObjectProvider.SetDataObject(iddo);
-			}
-			else
-			{
-				// Only support IStorageItem capable paths
-				var storageItems = ItemsSource.Select(x => VirtualStorageItem.FromPath(x.Inner.Id));
-				e.Data.SetStorageItems(storageItems, false);
-			}
+            if (!Shell32.SHCreateDataObject(null, apidl, null, out var ppDataObject).Succeeded)
+	            return;
+
+            e.Data.Properties["Files_ActionBinder"] = "Files_ShelfBinder";
+			ppDataObject.SetData(StandardDataFormats.StorageItems, apidl);
+			var dataObjectProvider = e.Data.As<Shell32.IDataObjectProvider>();
+			dataObjectProvider.SetDataObject(ppDataObject);
+
+
+            //var obj = new ShellDataObject();
+            //ppDataObject.SetData(StandardDataFormats.StorageItems, obj);
 		}
 
 		public IList<ShelfItem>? ItemsSource
diff --git a/src/Files.App/Views/Layouts/BaseLayoutPage.cs b/src/Files.App/Views/Layouts/BaseLayoutPage.cs
index c38181d24653..8793ff682f73 100644
--- a/src/Files.App/Views/Layouts/BaseLayoutPage.cs
+++ b/src/Files.App/Views/Layouts/BaseLayoutPage.cs
@@ -1137,17 +1137,24 @@ private async void Item_DragOver(object sender, DragEventArgs e)
 		protected virtual async void Item_Drop(object sender, DragEventArgs e)
 		{
 			var deferral = e.GetDeferral();
+			try
+			{
+				e.Handled = true;
+				_ = e.Data.Properties;
+				var exists = e.Data.Properties.TryGetValue("Files_ActionBinder", out var val);
+				_ = val;
 
-			e.Handled = true;
-
-			// Reset dragged over item
-			dragOverItem = null;
-
-			var item = GetItemFromElement(sender);
-			if (item is not null)
-				await ParentShellPageInstance!.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, (item as ShortcutItem)?.TargetPath ?? item.ItemPath, false, true, item.IsExecutable, item.IsScriptFile);
+				// Reset dragged over item
+				dragOverItem = null;
 
-			deferral.Complete();
+				var item = GetItemFromElement(sender);
+				if (item is not null)
+					await ParentShellPageInstance!.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, (item as ShortcutItem)?.TargetPath ?? item.ItemPath, false, true, item.IsExecutable, item.IsScriptFile);
+			}
+			finally
+			{
+				deferral.Complete();
+			}
 		}
 
 		protected void FileList_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)

From 7610dab8c322024409ce49152959a72d5275c453 Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Thu, 23 Jan 2025 00:39:42 +0100
Subject: [PATCH 02/10] Update ShelfPane.xaml.cs

---
 src/Files.App/UserControls/Pane/ShelfPane.xaml.cs | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
index 2bcc2a71a6a4..27d05acaa236 100644
--- a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
+++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
@@ -81,7 +81,10 @@ private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArg
 	            return;
 
             e.Data.Properties["Files_ActionBinder"] = "Files_ShelfBinder";
+
+			// TODO: Format is set correctly, but no items are present
 			ppDataObject.SetData(StandardDataFormats.StorageItems, apidl);
+
 			var dataObjectProvider = e.Data.As<Shell32.IDataObjectProvider>();
 			dataObjectProvider.SetDataObject(ppDataObject);
 

From f325868ae4e77b21508cf2a5fa7188ac09ad02f8 Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Thu, 23 Jan 2025 22:35:38 +0100
Subject: [PATCH 03/10] Update ShelfPane.xaml.cs

---
 .../UserControls/Pane/ShelfPane.xaml.cs          | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
index 27d05acaa236..6ce2d3cb6b66 100644
--- a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
+++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
@@ -77,20 +77,18 @@ private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArg
             if (apidl is null)
                 return;
 
-            if (!Shell32.SHCreateDataObject(null, apidl, null, out var ppDataObject).Succeeded)
-	            return;
+            if (!Shell32.SHGetDesktopFolder(out var pDesktop).Succeeded)
+				return;
 
-            e.Data.Properties["Files_ActionBinder"] = "Files_ShelfBinder";
+            if (!Shell32.SHGetIDListFromObject(pDesktop, out var pDesktopPidl).Succeeded)
+				return;
 
-			// TODO: Format is set correctly, but no items are present
-			ppDataObject.SetData(StandardDataFormats.StorageItems, apidl);
+            e.Data.Properties["Files_ActionBinder"] = "Files_ShelfBinder";
+			if (!Shell32.SHCreateDataObject(pDesktopPidl, apidl, null, out var ppDataObject).Succeeded)
+				return;
 
 			var dataObjectProvider = e.Data.As<Shell32.IDataObjectProvider>();
 			dataObjectProvider.SetDataObject(ppDataObject);
-
-
-            //var obj = new ShellDataObject();
-            //ppDataObject.SetData(StandardDataFormats.StorageItems, obj);
 		}
 
 		public IList<ShelfItem>? ItemsSource

From 11e55ce5ef470e48859530101adcb4621ff33fa2 Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Thu, 30 Jan 2025 22:54:20 +0100
Subject: [PATCH 04/10] WIP: Open flyout when dropping from Shelf

---
 .../ViewModels/Layouts/BaseLayoutViewModel.cs | 162 +++++++++++-------
 1 file changed, 97 insertions(+), 65 deletions(-)

diff --git a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
index 9e8c4e2472d2..76c77d3eca21 100644
--- a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
+++ b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
@@ -11,6 +11,7 @@
 using Windows.ApplicationModel.DataTransfer.DragDrop;
 using Windows.Storage;
 using Windows.System;
+using Microsoft.UI.Xaml.Controls;
 
 namespace Files.App.ViewModels.Layouts
 {
@@ -100,17 +101,22 @@ public async Task DragOverAsync(DragEventArgs e)
 				return;
 			}
 
-			if (FilesystemHelpers.HasDraggedStorageItems(e.DataView))
+			if (!FilesystemHelpers.HasDraggedStorageItems(e.DataView))
 			{
-				e.Handled = true;
-
-				var draggedItems = await FilesystemHelpers.GetDraggedStorageItems(e.DataView);
-
-				var pwd = _associatedInstance.ShellViewModel.WorkingDirectory.TrimPath();
-				var folderName = Path.IsPathRooted(pwd) && Path.GetPathRoot(pwd) == pwd ? Path.GetPathRoot(pwd) : Path.GetFileName(pwd);
+				deferral.Complete();
+				return;
+			}
+			
+			e.Handled = true;
+			var draggedItems = await FilesystemHelpers.GetDraggedStorageItems(e.DataView);
+			var pwd = _associatedInstance.ShellViewModel.WorkingDirectory.TrimPath();
+			var folderName = Path.IsPathRooted(pwd) && Path.GetPathRoot(pwd) == pwd ? Path.GetPathRoot(pwd) : Path.GetFileName(pwd);
 
+			try
+			{
 				// As long as one file doesn't already belong to this folder
-				if (_associatedInstance.InstanceViewModel.IsPageTypeSearchResults || draggedItems.Any() && draggedItems.AreItemsAlreadyInFolder(_associatedInstance.ShellViewModel.WorkingDirectory))
+				if (_associatedInstance.InstanceViewModel.IsPageTypeSearchResults || draggedItems.Any() &&
+				    draggedItems.AreItemsAlreadyInFolder(_associatedInstance.ShellViewModel.WorkingDirectory))
 				{
 					e.AcceptedOperation = DataPackageOperation.None;
 				}
@@ -120,80 +126,106 @@ public async Task DragOverAsync(DragEventArgs e)
 				}
 				else
 				{
-					try
+					e.DragUIOverride.IsCaptionVisible = true;
+					if (e.DataView.Properties.TryGetValue("Files_ActionBinder", out var actionBinder) && actionBinder is "Files_ShelfBinder")
 					{
-						e.DragUIOverride.IsCaptionVisible = true;
-						if (pwd.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal))
-						{
-							e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), folderName);
-							// Some applications such as Edge can't raise the drop event by the Move flag (#14008), so we set the Copy flag as well.
-							e.AcceptedOperation = DataPackageOperation.Move | DataPackageOperation.Copy;
-						}
-						else if (e.Modifiers.HasFlag(DragDropModifiers.Alt) || e.Modifiers.HasFlag(DragDropModifiers.Control | DragDropModifiers.Shift))
-						{
-							e.DragUIOverride.Caption = string.Format("LinkToFolderCaptionText".GetLocalizedResource(), folderName);
-							e.AcceptedOperation = DataPackageOperation.Link;
-						}
-						else if (e.Modifiers.HasFlag(DragDropModifiers.Control))
-						{
-							e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName);
-							e.AcceptedOperation = DataPackageOperation.Copy;
-						}
-						else if (e.Modifiers.HasFlag(DragDropModifiers.Shift))
-						{
-							e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), folderName);
-							// Some applications such as Edge can't raise the drop event by the Move flag (#14008), so we set the Copy flag as well.
-							e.AcceptedOperation = DataPackageOperation.Move | DataPackageOperation.Copy;
-						}
-						else if (draggedItems.Any(x =>
-							x.Item is ZipStorageFile ||
-							x.Item is ZipStorageFolder) ||
-							ZipStorageFolder.IsZipPath(pwd))
-						{
-							e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName);
-							e.AcceptedOperation = DataPackageOperation.Copy;
-						}
-						else if (draggedItems.AreItemsInSameDrive(_associatedInstance.ShellViewModel.WorkingDirectory))
-						{
-							e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), folderName);
-							// Some applications such as Edge can't raise the drop event by the Move flag (#14008), so we set the Copy flag as well.
-							e.AcceptedOperation = DataPackageOperation.Move | DataPackageOperation.Copy;
-						}
-						else
-						{
-							e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName);
-							e.AcceptedOperation = DataPackageOperation.Copy;
-						}
+						e.DragUIOverride.Caption = string.Format("LinkToFolderCaptionText".GetLocalizedResource(), folderName);
+						e.AcceptedOperation = DataPackageOperation.Link;
+					}
+					else if (pwd.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal))
+					{
+						e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), folderName);
 
-						_itemManipulationModel.ClearSelection();
+						// Some applications such as Edge can't raise the drop event by the Move flag (#14008), so we set the Copy flag as well.
+						e.AcceptedOperation = DataPackageOperation.Move | DataPackageOperation.Copy;
 					}
-					catch (COMException ex) when (ex.Message.Contains("RPC server is unavailable"))
+					else if (e.Modifiers.HasFlag(DragDropModifiers.Alt) || e.Modifiers.HasFlag(DragDropModifiers.Control | DragDropModifiers.Shift))
 					{
-						Logger?.LogDebug(ex, ex.Message);
+						e.DragUIOverride.Caption = string.Format("LinkToFolderCaptionText".GetLocalizedResource(), folderName);
+						e.AcceptedOperation = DataPackageOperation.Link;
 					}
+					else if (e.Modifiers.HasFlag(DragDropModifiers.Control))
+					{
+						e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName);
+						e.AcceptedOperation = DataPackageOperation.Copy;
+					}
+					else if (e.Modifiers.HasFlag(DragDropModifiers.Shift))
+					{
+						e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), folderName);
+
+						// Some applications such as Edge can't raise the drop event by the Move flag (#14008), so we set the Copy flag as well.
+						e.AcceptedOperation = DataPackageOperation.Move | DataPackageOperation.Copy;
+					}
+					else if (draggedItems.Any(x =>
+						         x.Item is ZipStorageFile ||
+						         x.Item is ZipStorageFolder) ||
+					         ZipStorageFolder.IsZipPath(pwd))
+					{
+						e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName);
+						e.AcceptedOperation = DataPackageOperation.Copy;
+					}
+					else if (draggedItems.AreItemsInSameDrive(_associatedInstance.ShellViewModel.WorkingDirectory))
+					{
+						e.DragUIOverride.Caption = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), folderName);
+
+						// Some applications such as Edge can't raise the drop event by the Move flag (#14008), so we set the Copy flag as well.
+						e.AcceptedOperation = DataPackageOperation.Move | DataPackageOperation.Copy;
+					}
+					else
+					{
+						e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName);
+						e.AcceptedOperation = DataPackageOperation.Copy;
+					}
+
+					_itemManipulationModel.ClearSelection();
 				}
 			}
-
-			deferral.Complete();
+			catch (COMException ex) when (ex.Message.Contains("RPC server is unavailable"))
+			{
+				Logger?.LogDebug(ex, ex.Message);
+			}
+			finally
+			{
+				deferral.Complete();
+			}
 		}
 
 		public async Task DropAsync(DragEventArgs e)
 		{
 			e.Handled = true;
+			var deferral = e.GetDeferral();
 
-			if (FilesystemHelpers.HasDraggedStorageItems(e.DataView))
+			try
 			{
-				var deferral = e.GetDeferral();
+				if (!FilesystemHelpers.HasDraggedStorageItems(e.DataView))
+					return;
 
-				try
-				{
-					await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true);
-					await _associatedInstance.RefreshIfNoWatcherExistsAsync();
-				}
-				finally
+				if (e.DataView.Properties.TryGetValue("Files_ActionBinder", out var actionBinder) && actionBinder is "Files_ShelfBinder")
 				{
-					deferral.Complete();
+					if (e.OriginalSource is not UIElement uiElement)
+						return;
+
+					var pwd = _associatedInstance.ShellViewModel.WorkingDirectory.TrimPath();
+					var folderName = Path.IsPathRooted(pwd) && Path.GetPathRoot(pwd) == pwd ? Path.GetPathRoot(pwd) : Path.GetFileName(pwd);
+					var menuFlyout = new MenuFlyout()
+					{
+						Items =
+						{
+							new MenuFlyoutItem() { Text = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName) },
+							new MenuFlyoutItem() { Text = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), folderName) },
+						}
+					};
+
+					menuFlyout.ShowAt(uiElement, e.GetPosition(uiElement));
+					return;
 				}
+
+				await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true);
+				await _associatedInstance.RefreshIfNoWatcherExistsAsync();
+			}
+			finally
+			{
+				deferral.Complete();
 			}
 		}
 

From 3bea105c266f3237fdb038e376b5a2746bb9c8c8 Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Sat, 15 Feb 2025 11:39:25 +0100
Subject: [PATCH 05/10] Implement Copy and Move operations

---
 src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
index 76c77d3eca21..e6236ed434c9 100644
--- a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
+++ b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
@@ -211,8 +211,10 @@ public async Task DropAsync(DragEventArgs e)
 					{
 						Items =
 						{
-							new MenuFlyoutItem() { Text = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName) },
-							new MenuFlyoutItem() { Text = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), folderName) },
+							new MenuFlyoutItem() { Text = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName), Command = new AsyncRelayCommand(async ct =>
+								await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(DataPackageOperation.Copy, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true))},
+							new MenuFlyoutItem() { Text = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), folderName), Command = new AsyncRelayCommand(async ct =>
+								await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(DataPackageOperation.Move, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true))}
 						}
 					};
 

From 17d1b5c9490f31cf1e090e34eeb089cc5153ce45 Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Sat, 15 Feb 2025 11:46:35 +0100
Subject: [PATCH 06/10] Change to INestedStorable

---
 src/Files.App/Data/Items/ShelfItem.cs             | 8 ++++----
 src/Files.App/UserControls/Pane/ShelfPane.xaml.cs | 8 +++-----
 2 files changed, 7 insertions(+), 9 deletions(-)

diff --git a/src/Files.App/Data/Items/ShelfItem.cs b/src/Files.App/Data/Items/ShelfItem.cs
index fc8b554c333d..461902fd2337 100644
--- a/src/Files.App/Data/Items/ShelfItem.cs
+++ b/src/Files.App/Data/Items/ShelfItem.cs
@@ -6,7 +6,7 @@
 namespace Files.App.Data.Items
 {
 	[Bindable(true)]
-	public sealed partial class ShelfItem : ObservableObject, IWrapper<IStorable>, IAsyncInitialize
+	public sealed partial class ShelfItem : ObservableObject, IWrapper<INestedStorable>, IAsyncInitialize
 	{
 		private readonly IImageService _imageService;
 		private readonly ICollection<ShelfItem> _sourceCollection;
@@ -16,9 +16,9 @@ public sealed partial class ShelfItem : ObservableObject, IWrapper<IStorable>, I
 		[ObservableProperty] private string? _Path;
 
 		/// <inheritdoc/>
-		public IStorable Inner { get; }
+		public INestedStorable Inner { get; }
 
-		public ShelfItem(IStorable storable, ICollection<ShelfItem> sourceCollection, IImage? icon = null)
+		public ShelfItem(INestedStorable storable, ICollection<ShelfItem> sourceCollection, IImage? icon = null)
 		{
 			_imageService = Ioc.Default.GetRequiredService<IImageService>();
 			_sourceCollection = sourceCollection;
@@ -35,7 +35,7 @@ public async Task InitAsync(CancellationToken cancellationToken = default)
 		}
 
 		[RelayCommand]
-		private void Remove()
+		public void Remove()
 		{
 			_sourceCollection.Remove(this);
 		}
diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
index 6ce2d3cb6b66..838ac36dd63a 100644
--- a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
+++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
@@ -1,14 +1,12 @@
 // Copyright (c) Files Community
 // Licensed under the MIT License.
 
-using System.Runtime.InteropServices;
 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 Vanara.Windows.Shell;
+using Windows.ApplicationModel.DataTransfer;
 using WinRT;
 using DragEventArgs = Microsoft.UI.Xaml.DragEventArgs;
 
@@ -52,8 +50,8 @@ private async void Shelf_Drop(object sender, DragEventArgs e)
 
 				var storable = item switch
 				{
-					StorageFileWithPath => (IStorable?)await storageService.TryGetFileAsync(item.Path),
-					StorageFolderWithPath => (IStorable?)await storageService.TryGetFolderAsync(item.Path),
+					StorageFileWithPath => (INestedStorable?)await storageService.TryGetFileAsync(item.Path),
+					StorageFolderWithPath => (INestedStorable?)await storageService.TryGetFolderAsync(item.Path),
 					_ => null
 				};
 

From 6875bdb6957217207bc4e50561429198d06fdd27 Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Sat, 15 Feb 2025 11:49:48 +0100
Subject: [PATCH 07/10] Formatting

---
 .../UserControls/Pane/ShelfPane.xaml.cs       | 22 +++++++++----------
 .../ViewModels/Layouts/BaseLayoutViewModel.cs |  8 +++----
 2 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
index 838ac36dd63a..21a1aff1ff47 100644
--- a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
+++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
@@ -65,23 +65,23 @@ private async void Shelf_Drop(object sender, DragEventArgs e)
 			}
 		}
 
-        private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
-        {
-            var apidl = SafetyExtensions.IgnoreExceptions(() => e.Items
-                .Cast<ShelfItem>()
-                .Select(x => new ShellItem(x.Inner.Id).PIDL)
-                .ToArray());
+		private void ListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
+		{
+			var apidl = SafetyExtensions.IgnoreExceptions(() => e.Items
+				.Cast<ShelfItem>()
+				.Select(x => new ShellItem(x.Inner.Id).PIDL)
+				.ToArray());
 
-            if (apidl is null)
-                return;
+			if (apidl is null)
+				return;
 
-            if (!Shell32.SHGetDesktopFolder(out var pDesktop).Succeeded)
+			if (!Shell32.SHGetDesktopFolder(out var pDesktop).Succeeded)
 				return;
 
-            if (!Shell32.SHGetIDListFromObject(pDesktop, out var pDesktopPidl).Succeeded)
+			if (!Shell32.SHGetIDListFromObject(pDesktop, out var pDesktopPidl).Succeeded)
 				return;
 
-            e.Data.Properties["Files_ActionBinder"] = "Files_ShelfBinder";
+			e.Data.Properties["Files_ActionBinder"] = "Files_ShelfBinder";
 			if (!Shell32.SHCreateDataObject(pDesktopPidl, apidl, null, out var ppDataObject).Succeeded)
 				return;
 
diff --git a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
index e6236ed434c9..5896e423930e 100644
--- a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
+++ b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
@@ -116,7 +116,7 @@ public async Task DragOverAsync(DragEventArgs e)
 			{
 				// As long as one file doesn't already belong to this folder
 				if (_associatedInstance.InstanceViewModel.IsPageTypeSearchResults || draggedItems.Any() &&
-				    draggedItems.AreItemsAlreadyInFolder(_associatedInstance.ShellViewModel.WorkingDirectory))
+					draggedItems.AreItemsAlreadyInFolder(_associatedInstance.ShellViewModel.WorkingDirectory))
 				{
 					e.AcceptedOperation = DataPackageOperation.None;
 				}
@@ -157,9 +157,9 @@ public async Task DragOverAsync(DragEventArgs e)
 						e.AcceptedOperation = DataPackageOperation.Move | DataPackageOperation.Copy;
 					}
 					else if (draggedItems.Any(x =>
-						         x.Item is ZipStorageFile ||
-						         x.Item is ZipStorageFolder) ||
-					         ZipStorageFolder.IsZipPath(pwd))
+								 x.Item is ZipStorageFile ||
+								 x.Item is ZipStorageFolder) ||
+							 ZipStorageFolder.IsZipPath(pwd))
 					{
 						e.DragUIOverride.Caption = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName);
 						e.AcceptedOperation = DataPackageOperation.Copy;

From 33dc46e0a6ac0142bb29948f0f3d32dc7fa6b9f7 Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Sat, 15 Feb 2025 11:59:05 +0100
Subject: [PATCH 08/10] Create ShelfViewModel.cs

---
 .../ViewModels/UserControls/ShelfViewModel.cs | 57 +++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 src/Files.App/ViewModels/UserControls/ShelfViewModel.cs

diff --git a/src/Files.App/ViewModels/UserControls/ShelfViewModel.cs b/src/Files.App/ViewModels/UserControls/ShelfViewModel.cs
new file mode 100644
index 000000000000..c753bcb6be54
--- /dev/null
+++ b/src/Files.App/ViewModels/UserControls/ShelfViewModel.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Files Community
+// Licensed under the MIT License.
+
+using System.Collections.Specialized;
+using Files.Shared.Utils;
+
+namespace Files.App.ViewModels.UserControls
+{
+	[Bindable(true)]
+	public sealed partial class ShelfViewModel : ObservableObject, IAsyncInitialize
+	{
+		private readonly Dictionary<string, IFolderWatcher> _watchers;
+
+		public ObservableCollection<ShelfItem> Items { get; }
+
+		public ShelfViewModel()
+		{
+			_watchers = new();
+			Items = new();
+			Items.CollectionChanged += Items_CollectionChanged;
+		}
+
+		/// <inheritdoc/>
+		public Task InitAsync(CancellationToken cancellationToken = default)
+		{
+			// TODO: Load persisted shelf items
+			return Task.CompletedTask;
+		}
+
+		private async void Items_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+		{
+			switch (e.Action)
+			{
+				case NotifyCollectionChangedAction.Add when e.NewItems is not null:
+				{
+					if (e.NewItems[0] is not INestedStorable nestedStorable)
+						return;
+
+					var parentPath = SystemIO.Path.GetDirectoryName(nestedStorable.Id) ?? string.Empty;
+					if (_watchers.ContainsKey(parentPath))
+						return;
+					
+					if (await nestedStorable.GetParentAsync() is not IMutableFolder mutableFolder)
+						return;
+
+					// TODO: Register IFolderWatcher
+
+					break;
+				}
+
+				case NotifyCollectionChangedAction.Remove:
+
+					break;
+			}
+		}
+	}
+}

From b1ddd15ebbfcddbbf148bd20abbeb656a660e00f Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Sun, 30 Mar 2025 10:21:42 +0200
Subject: [PATCH 09/10] Revert BaseLayoutViewModel to main

---
 .../ViewModels/Layouts/BaseLayoutViewModel.cs | 65 ++++++-------------
 1 file changed, 20 insertions(+), 45 deletions(-)

diff --git a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
index c360caed9f06..a4718776d142 100644
--- a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
+++ b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
@@ -11,7 +11,6 @@
 using Windows.ApplicationModel.DataTransfer.DragDrop;
 using Windows.Storage;
 using Windows.System;
-using Microsoft.UI.Xaml.Controls;
 
 namespace Files.App.ViewModels.Layouts
 {
@@ -101,7 +100,7 @@ public async Task DragOverAsync(DragEventArgs e)
 				return;
 			}
 
-			if (!FilesystemHelpers.HasDraggedStorageItems(e.DataView))
+			if (FilesystemHelpers.HasDraggedStorageItems(e.DataView))
 			{
 				e.Handled = true;
 
@@ -123,8 +122,7 @@ public async Task DragOverAsync(DragEventArgs e)
 				var draggedItems = await FilesystemHelpers.GetDraggedStorageItems(e.DataView);
 
 				// As long as one file doesn't already belong to this folder
-				if (_associatedInstance.InstanceViewModel.IsPageTypeSearchResults || draggedItems.Any() &&
-					draggedItems.AreItemsAlreadyInFolder(_associatedInstance.ShellViewModel.WorkingDirectory))
+				if (_associatedInstance.InstanceViewModel.IsPageTypeSearchResults || draggedItems.Any() && draggedItems.AreItemsAlreadyInFolder(_associatedInstance.ShellViewModel.WorkingDirectory))
 				{
 					e.AcceptedOperation = DataPackageOperation.None;
 				}
@@ -134,8 +132,7 @@ public async Task DragOverAsync(DragEventArgs e)
 				}
 				else
 				{
-					e.DragUIOverride.IsCaptionVisible = true;
-					if (e.DataView.Properties.TryGetValue("Files_ActionBinder", out var actionBinder) && actionBinder is "Files_ShelfBinder")
+					try
 					{
 						e.DragUIOverride.IsCaptionVisible = true;
 						if (workingDirectory.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal))
@@ -180,23 +177,21 @@ x.Item is ZipStorageFile ||
 							e.AcceptedOperation = DataPackageOperation.Copy;
 						}
 
-					_itemManipulationModel.ClearSelection();
+						_itemManipulationModel.ClearSelection();
+					}
+					catch (COMException ex) when (ex.Message.Contains("RPC server is unavailable"))
+					{
+						Logger?.LogDebug(ex, ex.Message);
+					}
 				}
 			}
-			catch (COMException ex) when (ex.Message.Contains("RPC server is unavailable"))
-			{
-				Logger?.LogDebug(ex, ex.Message);
-			}
-			finally
-			{
-				deferral.Complete();
-			}
+
+			deferral.Complete();
 		}
 
 		public async Task DropAsync(DragEventArgs e)
 		{
 			e.Handled = true;
-			var deferral = e.GetDeferral();
 
 
 			if (e.DataView.Contains(StandardDataFormats.Uri) && await e.DataView.GetUriAsync() is { } uri)
@@ -210,37 +205,17 @@ public async Task DropAsync(DragEventArgs e)
 
 			if (FilesystemHelpers.HasDraggedStorageItems(e.DataView))
 			{
-				if (!FilesystemHelpers.HasDraggedStorageItems(e.DataView))
-					return;
+				var deferral = e.GetDeferral();
 
-				if (e.DataView.Properties.TryGetValue("Files_ActionBinder", out var actionBinder) && actionBinder is "Files_ShelfBinder")
+				try
 				{
-					if (e.OriginalSource is not UIElement uiElement)
-						return;
-
-					var pwd = _associatedInstance.ShellViewModel.WorkingDirectory.TrimPath();
-					var folderName = Path.IsPathRooted(pwd) && Path.GetPathRoot(pwd) == pwd ? Path.GetPathRoot(pwd) : Path.GetFileName(pwd);
-					var menuFlyout = new MenuFlyout()
-					{
-						Items =
-						{
-							new MenuFlyoutItem() { Text = string.Format("CopyToFolderCaptionText".GetLocalizedResource(), folderName), Command = new AsyncRelayCommand(async ct =>
-								await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(DataPackageOperation.Copy, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true))},
-							new MenuFlyoutItem() { Text = string.Format("MoveToFolderCaptionText".GetLocalizedResource(), folderName), Command = new AsyncRelayCommand(async ct =>
-								await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(DataPackageOperation.Move, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true))}
-						}
-					};
-
-					menuFlyout.ShowAt(uiElement, e.GetPosition(uiElement));
-					return;
+					await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true);
+					await _associatedInstance.RefreshIfNoWatcherExistsAsync();
+				}
+				finally
+				{
+					deferral.Complete();
 				}
-
-				await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true);
-				await _associatedInstance.RefreshIfNoWatcherExistsAsync();
-			}
-			finally
-			{
-				deferral.Complete();
 			}
 		}
 
@@ -248,4 +223,4 @@ public void Dispose()
 		{
 		}
 	}
-}
+}
\ No newline at end of file

From 171c61eeadf46d0fe2976eb171e56a8a16d03ccf Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Sun, 30 Mar 2025 12:31:37 +0200
Subject: [PATCH 10/10] Connect ShelfViewModel

---
 src/Files.App/Data/Items/ShelfItem.cs         |  6 +-
 .../Helpers/Application/AppLifecycleHelper.cs |  1 +
 .../UserControls/Pane/ShelfPane.xaml          |  1 -
 .../UserControls/Pane/ShelfPane.xaml.cs       |  8 +--
 .../ViewModels/Layouts/BaseLayoutViewModel.cs | 46 ++++++++++---
 src/Files.App/ViewModels/MainPageViewModel.cs |  1 +
 .../ViewModels/UserControls/ShelfViewModel.cs | 65 +++++++++++++++++--
 src/Files.App/Views/MainPage.xaml             |  4 +-
 8 files changed, 104 insertions(+), 28 deletions(-)

diff --git a/src/Files.App/Data/Items/ShelfItem.cs b/src/Files.App/Data/Items/ShelfItem.cs
index 461902fd2337..3eb42efaa63f 100644
--- a/src/Files.App/Data/Items/ShelfItem.cs
+++ b/src/Files.App/Data/Items/ShelfItem.cs
@@ -6,7 +6,7 @@
 namespace Files.App.Data.Items
 {
 	[Bindable(true)]
-	public sealed partial class ShelfItem : ObservableObject, IWrapper<INestedStorable>, IAsyncInitialize
+	public sealed partial class ShelfItem : ObservableObject, IWrapper<IStorableChild>, IAsyncInitialize
 	{
 		private readonly IImageService _imageService;
 		private readonly ICollection<ShelfItem> _sourceCollection;
@@ -16,9 +16,9 @@ public sealed partial class ShelfItem : ObservableObject, IWrapper<INestedStorab
 		[ObservableProperty] private string? _Path;
 
 		/// <inheritdoc/>
-		public INestedStorable Inner { get; }
+		public IStorableChild Inner { get; }
 
-		public ShelfItem(INestedStorable storable, ICollection<ShelfItem> sourceCollection, IImage? icon = null)
+		public ShelfItem(IStorableChild storable, ICollection<ShelfItem> sourceCollection, IImage? icon = null)
 		{
 			_imageService = Ioc.Default.GetRequiredService<IImageService>();
 			_sourceCollection = sourceCollection;
diff --git a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs
index 227025f6f933..b55b761a70d3 100644
--- a/src/Files.App/Helpers/Application/AppLifecycleHelper.cs
+++ b/src/Files.App/Helpers/Application/AppLifecycleHelper.cs
@@ -236,6 +236,7 @@ public static IHost ConfigureHost()
 					.AddSingleton<InfoPaneViewModel>()
 					.AddSingleton<SidebarViewModel>()
 					.AddSingleton<DrivesViewModel>()
+					.AddSingleton<ShelfViewModel>()
 					.AddSingleton<StatusCenterViewModel>()
 					.AddSingleton<AppearanceViewModel>()
 					.AddTransient<HomeViewModel>()
diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml b/src/Files.App/UserControls/Pane/ShelfPane.xaml
index 987dedee0c29..8888d0b46526 100644
--- a/src/Files.App/UserControls/Pane/ShelfPane.xaml
+++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml
@@ -115,7 +115,6 @@
 				VerticalAlignment="Center"
 				Command="{x:Bind ClearCommand, Mode=OneWay}"
 				Content="{helpers:ResourceString Name=ClearItems}" />
-
 		</StackPanel>
 	</Grid>
 </UserControl>
diff --git a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
index 57f641b7e826..fcb1c5c38ea0 100644
--- a/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
+++ b/src/Files.App/UserControls/Pane/ShelfPane.xaml.cs
@@ -7,7 +7,6 @@
 using Vanara.PInvoke;
 using Vanara.Windows.Shell;
 using Windows.ApplicationModel.DataTransfer;
-using OwlCore.Storage;
 using WinRT;
 using DragEventArgs = Microsoft.UI.Xaml.DragEventArgs;
 
@@ -17,9 +16,6 @@ public sealed partial class ShelfPane : UserControl
 	{
 		public ShelfPane()
 		{
-			// TODO: [Shelf] Remove once view model is connected
-			ItemsSource = new ObservableCollection<ShelfItem>();
-
 			InitializeComponent();
 		}
 
@@ -51,8 +47,8 @@ private async void Shelf_Drop(object sender, DragEventArgs e)
 
 				var storable = item switch
 				{
-					StorageFileWithPath => (INestedStorable?)await storageService.TryGetFileAsync(item.Path),
-					StorageFolderWithPath => (INestedStorable?)await storageService.TryGetFolderAsync(item.Path),
+					StorageFileWithPath => (IStorableChild?)await storageService.TryGetFileAsync(item.Path),
+					StorageFolderWithPath => (IStorableChild?)await storageService.TryGetFolderAsync(item.Path),
 					_ => null
 				};
 
diff --git a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
index a4718776d142..f14c3ac46b95 100644
--- a/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
+++ b/src/Files.App/ViewModels/Layouts/BaseLayoutViewModel.cs
@@ -3,6 +3,7 @@
 
 using Microsoft.Extensions.Logging;
 using Microsoft.UI.Xaml;
+using Microsoft.UI.Xaml.Controls;
 using Microsoft.UI.Xaml.Input;
 using System.IO;
 using System.Runtime.InteropServices;
@@ -135,7 +136,12 @@ public async Task DragOverAsync(DragEventArgs e)
 					try
 					{
 						e.DragUIOverride.IsCaptionVisible = true;
-						if (workingDirectory.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal))
+						if (e.DataView.Properties.TryGetValue("Files_ActionBinder", out var actionBinder) && actionBinder is "Files_ShelfBinder")
+						{
+							e.DragUIOverride.Caption = string.Format(Strings.LinkToFolderCaptionText.GetLocalizedResource(), folderName);
+							e.AcceptedOperation = DataPackageOperation.Link;
+						}
+						else if (workingDirectory.StartsWith(Constants.UserEnvironmentPaths.RecycleBinPath, StringComparison.Ordinal))
 						{
 							e.DragUIOverride.Caption = string.Format(Strings.MoveToFolderCaptionText.GetLocalizedResource(), folderName);
 							// Some applications such as Edge can't raise the drop event by the Move flag (#14008), so we set the Copy flag as well.
@@ -192,8 +198,6 @@ x.Item is ZipStorageFile ||
 		public async Task DropAsync(DragEventArgs e)
 		{
 			e.Handled = true;
-
-
 			if (e.DataView.Contains(StandardDataFormats.Uri) && await e.DataView.GetUriAsync() is { } uri)
 			{
 				if (GitHelpers.IsValidRepoUrl(uri.ToString()))
@@ -203,20 +207,42 @@ public async Task DropAsync(DragEventArgs e)
 				}
 			}
 
-			if (FilesystemHelpers.HasDraggedStorageItems(e.DataView))
+			var deferral = e.GetDeferral();
+			try
 			{
-				var deferral = e.GetDeferral();
+				if (!FilesystemHelpers.HasDraggedStorageItems(e.DataView))
+					return;
 
-				try
+				if (e.DataView.Properties.TryGetValue("Files_ActionBinder", out var actionBinder) && actionBinder is "Files_ShelfBinder")
 				{
-					await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true);
-					await _associatedInstance.RefreshIfNoWatcherExistsAsync();
+					if (e.OriginalSource is not UIElement uiElement)
+						return;
+
+					var pwd = _associatedInstance.ShellViewModel.WorkingDirectory.TrimPath();
+					var folderName = Path.IsPathRooted(pwd) && Path.GetPathRoot(pwd) == pwd ? Path.GetPathRoot(pwd) : Path.GetFileName(pwd);
+					var menuFlyout = new MenuFlyout()
+					{
+						Items =
+						{
+							new MenuFlyoutItem() { Text = string.Format(Strings.CopyToFolderCaptionText.GetLocalizedResource(), folderName), Command = new AsyncRelayCommand(async ct =>
+								await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(DataPackageOperation.Copy, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true)) },
+							new MenuFlyoutItem() { Text = string.Format(Strings.MoveToFolderCaptionText.GetLocalizedResource(), folderName), Command = new AsyncRelayCommand(async ct =>
+								await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(DataPackageOperation.Move, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true)) }
+						}
+					};
+
+					menuFlyout.ShowAt(uiElement, e.GetPosition(uiElement));
 				}
-				finally
+				else
 				{
-					deferral.Complete();
+					await _associatedInstance.FilesystemHelpers.PerformOperationTypeAsync(e.AcceptedOperation, e.DataView, _associatedInstance.ShellViewModel.WorkingDirectory, false, true);
+					await _associatedInstance.RefreshIfNoWatcherExistsAsync();
 				}
 			}
+			finally
+			{
+				deferral.Complete();
+			}
 		}
 
 		public void Dispose()
diff --git a/src/Files.App/ViewModels/MainPageViewModel.cs b/src/Files.App/ViewModels/MainPageViewModel.cs
index 64b51d54f6a7..aa2255e35f1f 100644
--- a/src/Files.App/ViewModels/MainPageViewModel.cs
+++ b/src/Files.App/ViewModels/MainPageViewModel.cs
@@ -25,6 +25,7 @@ public sealed partial class MainPageViewModel : ObservableObject
 		private IUserSettingsService UserSettingsService { get; } = Ioc.Default.GetRequiredService<IUserSettingsService>();
 		private IResourcesService ResourcesService { get; } = Ioc.Default.GetRequiredService<IResourcesService>();
 		private DrivesViewModel DrivesViewModel { get; } = Ioc.Default.GetRequiredService<DrivesViewModel>();
+		public ShelfViewModel ShelfViewModel { get; } = Ioc.Default.GetRequiredService<ShelfViewModel>();
 
 		// Properties
 
diff --git a/src/Files.App/ViewModels/UserControls/ShelfViewModel.cs b/src/Files.App/ViewModels/UserControls/ShelfViewModel.cs
index c753bcb6be54..f5fdc8f62a10 100644
--- a/src/Files.App/ViewModels/UserControls/ShelfViewModel.cs
+++ b/src/Files.App/ViewModels/UserControls/ShelfViewModel.cs
@@ -9,7 +9,7 @@ namespace Files.App.ViewModels.UserControls
 	[Bindable(true)]
 	public sealed partial class ShelfViewModel : ObservableObject, IAsyncInitialize
 	{
-		private readonly Dictionary<string, IFolderWatcher> _watchers;
+		private readonly Dictionary<string, (IFolderWatcher, int)> _watchers;
 
 		public ObservableCollection<ShelfItem> Items { get; }
 
@@ -27,30 +27,81 @@ public Task InitAsync(CancellationToken cancellationToken = default)
 			return Task.CompletedTask;
 		}
 
+		[RelayCommand]
+		private void ClearItems()
+		{
+			Items.Clear();
+		}
+
 		private async void Items_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
 		{
 			switch (e.Action)
 			{
 				case NotifyCollectionChangedAction.Add when e.NewItems is not null:
 				{
-					if (e.NewItems[0] is not INestedStorable nestedStorable)
+					if (e.NewItems[0] is not ShelfItem shelfItem)
 						return;
 
-					var parentPath = SystemIO.Path.GetDirectoryName(nestedStorable.Id) ?? string.Empty;
-					if (_watchers.ContainsKey(parentPath))
+					var parentPath = SystemIO.Path.GetDirectoryName(shelfItem.Inner.Id) ?? string.Empty;
+					if (_watchers.TryGetValue(parentPath, out var reference))
+					{
+						// Only increase the reference count if the watcher already exists
+						reference.Item2++;
 						return;
+					}
 					
-					if (await nestedStorable.GetParentAsync() is not IMutableFolder mutableFolder)
+					if (await shelfItem.Inner.GetParentAsync() is not IMutableFolder mutableFolder)
 						return;
 
-					// TODO: Register IFolderWatcher
+					// Register new watcher
+					var watcher = await mutableFolder.GetFolderWatcherAsync();
+					watcher.CollectionChanged += Watcher_CollectionChanged;
 
+					_watchers.Add(parentPath, (watcher, 1));
 					break;
 				}
 
-				case NotifyCollectionChangedAction.Remove:
+				case NotifyCollectionChangedAction.Remove when e.OldItems is not null:
+				{
+					if (e.OldItems[0] is not ShelfItem shelfItem)
+						return;
+
+					var parentPath = SystemIO.Path.GetDirectoryName(shelfItem.Inner.Id) ?? string.Empty;
+					if (!_watchers.TryGetValue(parentPath, out var reference))
+						return;
+
+					// Decrease the reference count and remove the watcher if no references are present
+					reference.Item2--;
+					if (reference.Item2 < 1)
+					{
+						reference.Item1.CollectionChanged -= Watcher_CollectionChanged;
+						reference.Item1.Dispose();
+						_watchers.Remove(parentPath);
+					}
 
 					break;
+				}
+			}
+		}
+
+		private async void Watcher_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+		{
+			if (sender is not IFolderWatcher watcher)
+				return;
+
+			switch (e.Action)
+			{
+				case NotifyCollectionChangedAction.Remove when e.OldItems is not null:
+				{
+					// Remove the matching item notified from the watcher
+					var item = e.OldItems.Cast<IStorable>().ElementAt(0);
+					var itemToRemove = Items.FirstOrDefault(x => x.Inner.Id == item.Id);
+					if (itemToRemove is null)
+						return;
+
+					await MainWindow.Instance.DispatcherQueue.EnqueueOrInvokeAsync(() => Items.Remove(itemToRemove));
+					break;
+				}
 			}
 		}
 	}
diff --git a/src/Files.App/Views/MainPage.xaml b/src/Files.App/Views/MainPage.xaml
index 806d3b434327..06a3ff59f6c2 100644
--- a/src/Files.App/Views/MainPage.xaml
+++ b/src/Files.App/Views/MainPage.xaml
@@ -253,7 +253,9 @@
 						Grid.RowSpan="5"
 						Grid.Column="3"
 						Margin="4,0,0,8"
-						x:Load="{x:Bind ViewModel.ShowShelfPane, Mode=OneWay}" />
+						x:Load="{x:Bind ViewModel.ShowShelfPane, Mode=OneWay}"
+						ClearCommand="{x:Bind ViewModel.ShelfViewModel.ClearItemsCommand}"
+						ItemsSource="{x:Bind ViewModel.ShelfViewModel.Items}" />
 				</Grid>
 			</controls:SidebarView.InnerContent>