From 8a85d952d5ff346784d7abf6dc477a501535199e Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:55:08 -0400 Subject: [PATCH 1/3] Feature: Added support for pasting items as shortcuts --- .../FileSystem/PasteItemAsShortcutAction.cs | 60 +++++++++++++++++++ .../Data/Commands/Manager/CommandCodes.cs | 1 + .../Data/Commands/Manager/CommandManager.cs | 2 + .../Data/Commands/Manager/ICommandManager.cs | 1 + .../ContentPageContextFlyoutFactory.cs | 1 + .../Helpers/UI/UIFilesystemHelpers.cs | 20 +++++++ src/Files.App/Strings/en-US/Resources.resw | 6 ++ 7 files changed, 91 insertions(+) create mode 100644 src/Files.App/Actions/FileSystem/PasteItemAsShortcutAction.cs diff --git a/src/Files.App/Actions/FileSystem/PasteItemAsShortcutAction.cs b/src/Files.App/Actions/FileSystem/PasteItemAsShortcutAction.cs new file mode 100644 index 000000000000..86482877f5b6 --- /dev/null +++ b/src/Files.App/Actions/FileSystem/PasteItemAsShortcutAction.cs @@ -0,0 +1,60 @@ +// Copyright (c) 2024 Files Community +// Licensed under the MIT License. See the LICENSE. + +namespace Files.App.Actions +{ + internal sealed class PasteItemAsShortcutAction : ObservableObject, IAction + { + private readonly IContentPageContext context; + + public string Label + => Strings.PasteShortcut.GetLocalizedResource(); + + public string Description + => Strings.PasteShortcutDescription.GetLocalizedResource(); + + public RichGlyph Glyph + => new(themedIconStyle: "App.ThemedIcons.Paste"); + + public bool IsExecutable + => GetIsExecutable(); + + public PasteItemAsShortcutAction() + { + context = Ioc.Default.GetRequiredService(); + + context.PropertyChanged += Context_PropertyChanged; + App.AppModel.PropertyChanged += AppModel_PropertyChanged; + } + + public async Task ExecuteAsync(object? parameter = null) + { + if (context.ShellPage is null) + return; + + string path = context.ShellPage.ShellViewModel.WorkingDirectory; + await UIFilesystemHelpers.PasteItemAsShortcutAsync(path, context.ShellPage); + } + + public bool GetIsExecutable() + { + return + App.AppModel.IsPasteEnabled && + context.PageType != ContentPageTypes.Home && + context.PageType != ContentPageTypes.RecycleBin && + context.PageType != ContentPageTypes.SearchResults; + } + + private void Context_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName is nameof(IContentPageContext.PageType)) + OnPropertyChanged(nameof(IsExecutable)); + } + + private void AppModel_PropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName is nameof(AppModel.IsPasteEnabled)) + OnPropertyChanged(nameof(IsExecutable)); + } + } +} diff --git a/src/Files.App/Data/Commands/Manager/CommandCodes.cs b/src/Files.App/Data/Commands/Manager/CommandCodes.cs index 54df5eaaa3b2..60480f36a7bb 100644 --- a/src/Files.App/Data/Commands/Manager/CommandCodes.cs +++ b/src/Files.App/Data/Commands/Manager/CommandCodes.cs @@ -35,6 +35,7 @@ public enum CommandCodes CopyPathWithQuotes, CutItem, PasteItem, + PasteItemAsShortcut, PasteItemToSelection, DeleteItem, DeleteItemPermanently, diff --git a/src/Files.App/Data/Commands/Manager/CommandManager.cs b/src/Files.App/Data/Commands/Manager/CommandManager.cs index 2dcda76eaf63..8766847953e6 100644 --- a/src/Files.App/Data/Commands/Manager/CommandManager.cs +++ b/src/Files.App/Data/Commands/Manager/CommandManager.cs @@ -87,6 +87,7 @@ public IRichCommand this[HotKey hotKey] public IRichCommand CopyPathWithQuotes => commands[CommandCodes.CopyPathWithQuotes]; public IRichCommand CutItem => commands[CommandCodes.CutItem]; public IRichCommand PasteItem => commands[CommandCodes.PasteItem]; + public IRichCommand PasteItemAsShortcut => commands[CommandCodes.PasteItemAsShortcut]; public IRichCommand PasteItemToSelection => commands[CommandCodes.PasteItemToSelection]; public IRichCommand DeleteItem => commands[CommandCodes.DeleteItem]; public IRichCommand DeleteItemPermanently => commands[CommandCodes.DeleteItemPermanently]; @@ -281,6 +282,7 @@ public IEnumerator GetEnumerator() => [CommandCodes.CopyPathWithQuotes] = new CopyPathWithQuotesAction(), [CommandCodes.CutItem] = new CutItemAction(), [CommandCodes.PasteItem] = new PasteItemAction(), + [CommandCodes.PasteItemAsShortcut] = new PasteItemAsShortcutAction(), [CommandCodes.PasteItemToSelection] = new PasteItemToSelectionAction(), [CommandCodes.DeleteItem] = new DeleteItemAction(), [CommandCodes.DeleteItemPermanently] = new DeleteItemPermanentlyAction(), diff --git a/src/Files.App/Data/Commands/Manager/ICommandManager.cs b/src/Files.App/Data/Commands/Manager/ICommandManager.cs index 8d215da50f1a..43acdbdaeb4c 100644 --- a/src/Files.App/Data/Commands/Manager/ICommandManager.cs +++ b/src/Files.App/Data/Commands/Manager/ICommandManager.cs @@ -39,6 +39,7 @@ public interface ICommandManager : IEnumerable IRichCommand CopyPathWithQuotes { get; } IRichCommand CutItem { get; } IRichCommand PasteItem { get; } + IRichCommand PasteItemAsShortcut { get; } IRichCommand PasteItemToSelection { get; } IRichCommand DeleteItem { get; } IRichCommand DeleteItemPermanently { get; } diff --git a/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.cs b/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.cs index 57db349b7f07..67cf54c347bd 100644 --- a/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.cs +++ b/src/Files.App/Data/Factories/ContentPageContextFlyoutFactory.cs @@ -459,6 +459,7 @@ public static List GetBaseItemMenuItems( IsPrimary = true, IsVisible = true, }.Build(), + new ContextMenuFlyoutItemViewModelBuilder(Commands.PasteItemAsShortcut).Build(), new ContextMenuFlyoutItemViewModelBuilder(Commands.CopyItemPath) { IsVisible = UserSettingsService.GeneralSettingsService.ShowCopyPath diff --git a/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs b/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs index be4d8e8415c2..f0b45a202020 100644 --- a/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs +++ b/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs @@ -26,6 +26,26 @@ public static async Task PasteItemAsync(string destinationPath, IShellPage assoc } } + public static async Task PasteItemAsShortcutAsync(string destinationPath, IShellPage associatedInstance) + { + FilesystemResult packageView = await FilesystemTasks.Wrap(() => Task.FromResult(Clipboard.GetContent())); + if (packageView.Result.Contains(StandardDataFormats.StorageItems)) + { + var items = await packageView.Result.GetStorageItemsAsync(); + foreach (IStorageItem item in items) + { + var fileName = FilesystemHelpers.GetShortcutNamingPreference(item.Name); + var filePath = Path.Combine(destinationPath ?? string.Empty, fileName); + + if (!await FileOperationsHelpers.CreateOrUpdateLinkAsync(filePath, item.Path)) + await HandleShortcutCannotBeCreated(fileName, item.Path); + } + } + + if (associatedInstance is not null) + await associatedInstance.RefreshIfNoWatcherExistsAsync(); + } + public static async Task RenameFileItemAsync(ListedItem item, string newName, IShellPage associatedInstance, bool showExtensionDialog = true) { if (item is AlternateStreamItem ads) // For alternate streams Name is not a substring ItemNameRaw diff --git a/src/Files.App/Strings/en-US/Resources.resw b/src/Files.App/Strings/en-US/Resources.resw index 03c9666346bd..03223e6e5268 100644 --- a/src/Files.App/Strings/en-US/Resources.resw +++ b/src/Files.App/Strings/en-US/Resources.resw @@ -252,6 +252,9 @@ Paste + + Paste shortcut + New @@ -2411,6 +2414,9 @@ Paste item(s) from clipboard to current folder + + Paste item(s) from clipboard to current folder as shortcuts + Paste item(s) from clipboard to selected folder From 48b351fa09b6960e35d91f61ab67b095a603b53b Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Wed, 30 Oct 2024 21:21:15 -0400 Subject: [PATCH 2/3] Update PasteItemAsShortcutAction.cs --- .../Actions/FileSystem/PasteItemAsShortcutAction.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Files.App/Actions/FileSystem/PasteItemAsShortcutAction.cs b/src/Files.App/Actions/FileSystem/PasteItemAsShortcutAction.cs index 86482877f5b6..6c29c3b844af 100644 --- a/src/Files.App/Actions/FileSystem/PasteItemAsShortcutAction.cs +++ b/src/Files.App/Actions/FileSystem/PasteItemAsShortcutAction.cs @@ -27,13 +27,13 @@ public PasteItemAsShortcutAction() App.AppModel.PropertyChanged += AppModel_PropertyChanged; } - public async Task ExecuteAsync(object? parameter = null) + public Task ExecuteAsync(object? parameter = null) { if (context.ShellPage is null) - return; + return Task.CompletedTask; string path = context.ShellPage.ShellViewModel.WorkingDirectory; - await UIFilesystemHelpers.PasteItemAsShortcutAsync(path, context.ShellPage); + return UIFilesystemHelpers.PasteItemAsShortcutAsync(path, context.ShellPage); } public bool GetIsExecutable() From e1c980d2dc8b9a3073454aa0e95102325778c7d1 Mon Sep 17 00:00:00 2001 From: Yair <39923744+yaira2@users.noreply.github.com> Date: Thu, 31 Oct 2024 11:29:37 -0400 Subject: [PATCH 3/3] Update UIFilesystemHelpers.cs --- src/Files.App/Helpers/UI/UIFilesystemHelpers.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs b/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs index f0b45a202020..da6408f05f33 100644 --- a/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs +++ b/src/Files.App/Helpers/UI/UIFilesystemHelpers.cs @@ -2,7 +2,6 @@ // Licensed under the MIT License. See the LICENSE. using Files.App.Dialogs; -using Files.App.Storage.Storables; using Microsoft.Extensions.Logging; using System.IO; using System.Net; @@ -32,14 +31,14 @@ public static async Task PasteItemAsShortcutAsync(string destinationPath, IShell if (packageView.Result.Contains(StandardDataFormats.StorageItems)) { var items = await packageView.Result.GetStorageItemsAsync(); - foreach (IStorageItem item in items) + await Task.WhenAll(items.Select(async item => { var fileName = FilesystemHelpers.GetShortcutNamingPreference(item.Name); var filePath = Path.Combine(destinationPath ?? string.Empty, fileName); if (!await FileOperationsHelpers.CreateOrUpdateLinkAsync(filePath, item.Path)) await HandleShortcutCannotBeCreated(fileName, item.Path); - } + })); } if (associatedInstance is not null)