diff --git a/src/Files.App/Actions/FileSystem/PasteItemAsShortcutAction.cs b/src/Files.App/Actions/FileSystem/PasteItemAsShortcutAction.cs new file mode 100644 index 000000000000..6c29c3b844af --- /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 Task ExecuteAsync(object? parameter = null) + { + if (context.ShellPage is null) + return Task.CompletedTask; + + string path = context.ShellPage.ShellViewModel.WorkingDirectory; + return 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..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; @@ -26,6 +25,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(); + 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) + 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