diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 896fc9922b7..c331c498568 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -75,7 +75,9 @@ public List LoadContextMenus(Result selectedResult) { Settings.QuickAccessLinks.Add(new AccessLink { - Path = record.FullPath, Type = record.Type + Name = record.FullPath.GetPathName(), + Path = record.FullPath, + Type = record.Type }); Context.API.ShowMsg(Context.API.GetTranslation("plugin_explorer_addfilefoldersuccess"), diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/PathHelper.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/PathHelper.cs new file mode 100644 index 00000000000..7763d966262 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/PathHelper.cs @@ -0,0 +1,21 @@ +using System; +using System.IO; +using System.Linq; +using Flow.Launcher.Plugin.Explorer.Search; + +namespace Flow.Launcher.Plugin.Explorer.Helper; + +public static class PathHelper +{ + public static string GetPathName(this string selectedPath) + { + if (string.IsNullOrEmpty(selectedPath)) return string.Empty; + var path = selectedPath.EndsWith(Constants.DirectorySeparator) ? selectedPath[0..^1] : selectedPath; + + if (path.EndsWith(':')) + return path[0..^1] + " Drive"; + + return path.Split(new[] { Path.DirectorySeparatorChar }, StringSplitOptions.None) + .Last(); + } +} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index 9f60aaa4340..27d1bfeaba5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -5,6 +5,8 @@ Please make a selection first + Please select a folder path. + Please choose a different name or folder path. Please select a folder link Are you sure you want to delete {0}? Are you sure you want to permanently delete this file? @@ -27,6 +29,7 @@ Add General Setting Customise Action Keywords + Customise Quick Access Quick Access Links Everything Setting Preview Panel @@ -92,6 +95,7 @@ Permanently delete current file Permanently delete current folder Path: + Name: Delete the selected Run as different user Run the selected using a different user account diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 1c5d074a0b8..28382020435 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -35,6 +35,7 @@ public Task InitAsync(PluginInitContext context) Context = context; Settings = context.API.LoadSettingJsonStorage(); + FillQuickAccessLinkNames(); viewModel = new SettingsViewModel(context, Settings); @@ -95,5 +96,17 @@ public string GetTranslatedPluginDescription() { return Context.API.GetTranslation("plugin_explorer_plugin_description"); } + + private void FillQuickAccessLinkNames() + { + // Legacy version does not have names for quick access links, so we fill them with the path name. + foreach (var link in Settings.QuickAccessLinks) + { + if (string.IsNullOrWhiteSpace(link.Name)) + { + link.Name = link.Path.GetPathName(); + } + } + } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/AccessLink.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/AccessLink.cs index 1975211f9bc..5dd6162e8c5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/AccessLink.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/AccessLink.cs @@ -1,8 +1,4 @@ -using System; -using System.Linq; -using System.Text.Json.Serialization; - -namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks +namespace Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks { public class AccessLink { @@ -10,20 +6,6 @@ public class AccessLink public ResultType Type { get; set; } = ResultType.Folder; - [JsonIgnore] - public string Name - { - get - { - var path = Path.EndsWith(Constants.DirectorySeparator) ? Path[0..^1] : Path; - - if (path.EndsWith(':')) - return path[0..^1] + " Drive"; - - return path.Split(new[] { System.IO.Path.DirectorySeparatorChar }, StringSplitOptions.None) - .Last(); - } - } + public string Name { get; set; } } - } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index fb33dacab01..59d3a5cfda6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -9,6 +9,7 @@ using System.Windows; using System.Windows.Forms; using CommunityToolkit.Mvvm.Input; +using Flow.Launcher.Plugin.Explorer.Helper; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.Search.Everything.Exceptions; @@ -328,14 +329,10 @@ public void AppendLink(string containerName, AccessLink link) } [RelayCommand] - private void EditLink(object commandParameter) + private void EditIndexSearchExcludePaths() { - var (selectedLink, collection) = commandParameter switch - { - "QuickAccessLink" => (SelectedQuickAccessLink, Settings.QuickAccessLinks), - "IndexSearchExcludedPaths" => (SelectedIndexSearchExcludedPath, Settings.IndexSearchExcludedSubdirectoryPaths), - _ => throw new ArgumentOutOfRangeException(nameof(commandParameter)) - }; + var selectedLink = SelectedIndexSearchExcludedPath; + var collection = Settings.IndexSearchExcludedSubdirectoryPaths; if (selectedLink is null) { @@ -354,28 +351,18 @@ private void EditLink(object commandParameter) collection.Remove(selectedLink); collection.Add(new AccessLink { - Path = path, Type = selectedLink.Type, + Path = path, Type = selectedLink.Type, Name = path.GetPathName() }); - } - - private void ShowUnselectedMessage() - { - var warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning"); - Context.API.ShowMsgBox(warning); + Save(); } [RelayCommand] - private void AddLink(object commandParameter) + private void AddIndexSearchExcludePaths() { - var container = commandParameter switch - { - "QuickAccessLink" => Settings.QuickAccessLinks, - "IndexSearchExcludedPaths" => Settings.IndexSearchExcludedSubdirectoryPaths, - _ => throw new ArgumentOutOfRangeException(nameof(commandParameter)) - }; - - ArgumentNullException.ThrowIfNull(container); + var container = Settings.IndexSearchExcludedSubdirectoryPaths; + if (container is null) return; + var folderBrowserDialog = new FolderBrowserDialog(); if (folderBrowserDialog.ShowDialog() != DialogResult.OK) @@ -383,16 +370,47 @@ private void AddLink(object commandParameter) var newAccessLink = new AccessLink { + Name = folderBrowserDialog.SelectedPath.GetPathName(), Path = folderBrowserDialog.SelectedPath }; container.Add(newAccessLink); + Save(); + } + + [RelayCommand] + private void EditQuickAccessLink() + { + var selectedLink = SelectedQuickAccessLink; + var collection = Settings.QuickAccessLinks; + + if (selectedLink is null) + { + ShowUnselectedMessage(); + return; + } + + var quickAccessLinkSettings = new QuickAccessLinkSettings(collection, SelectedQuickAccessLink); + if (quickAccessLinkSettings.ShowDialog() == true) + { + Save(); + } + } + + [RelayCommand] + private void AddQuickAccessLink() + { + var quickAccessLinkSettings = new QuickAccessLinkSettings(Settings.QuickAccessLinks); + if (quickAccessLinkSettings.ShowDialog() == true) + { + Save(); + } } [RelayCommand] - private void RemoveLink(object obj) + private void RemoveLink(object commandParameter) { - if (obj is not string container) return; + if (commandParameter is not string container) return; switch (container) { @@ -407,10 +425,16 @@ private void RemoveLink(object obj) } Save(); } + + private void ShowUnselectedMessage() + { + var warning = Context.API.GetTranslation("plugin_explorer_make_selection_warning"); + Context.API.ShowMsgBox(warning); + } #endregion - private string? PromptUserSelectPath(ResultType type, string? initialDirectory = null) + private static string? PromptUserSelectPath(ResultType type, string? initialDirectory = null) { string? path = null; diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index 47d53d46360..001a9e16832 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -92,41 +92,43 @@ + Background="{TemplateBinding Background}" + BorderBrush="{TemplateBinding BorderBrush}" + BorderThickness="{TemplateBinding BorderThickness}" + SnapsToDevicePixels="true"> - + x:Name="HeaderSite" + MinWidth="0" + MinHeight="0" + Margin="0" + Padding="{TemplateBinding Padding}" + HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" + Content="{TemplateBinding Header}" + ContentTemplate="{TemplateBinding HeaderTemplate}" + ContentTemplateSelector="{TemplateBinding HeaderTemplateSelector}" + DockPanel.Dock="Top" + FocusVisualStyle="{StaticResource ExpanderHeaderFocusVisual}" + FontFamily="{TemplateBinding FontFamily}" + FontSize="{TemplateBinding FontSize}" + FontStretch="{TemplateBinding FontStretch}" + FontStyle="{TemplateBinding FontStyle}" + FontWeight="{TemplateBinding FontWeight}" + Foreground="{TemplateBinding Foreground}" + IsChecked="{Binding IsExpanded, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" + Style="{StaticResource ExpanderHeaderRightArrowStyle}" /> + + x:Name="ExpandSite" + Margin="{TemplateBinding Padding}" + HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" + VerticalAlignment="{TemplateBinding VerticalContentAlignment}" + DockPanel.Dock="Bottom" + Focusable="false" /> @@ -141,13 +143,17 @@ + Storyboard.TargetName="ContentPresenterBorder" + Storyboard.TargetProperty="(Border.LayoutTransform).(ScaleTransform.ScaleY)" + From="0.0" + To="1.0" + Duration="0:0:0" /> + Storyboard.TargetName="ContentPresenterBorder" + Storyboard.TargetProperty="(Border.Opacity)" + From="0.0" + To="1.0" + Duration="0:0:0" /> @@ -155,13 +161,17 @@ + Storyboard.TargetName="ContentPresenterBorder" + Storyboard.TargetProperty="(Border.LayoutTransform).(ScaleTransform.ScaleY)" + From="1.0" + To="0.0" + Duration="0:0:0" /> + Storyboard.TargetName="ContentPresenterBorder" + Storyboard.TargetProperty="(Border.Opacity)" + From="1.0" + To="0.0" + Duration="0:0:0" /> @@ -734,10 +744,29 @@ BorderThickness="1" DragEnter="lbxAccessLinks_DragEnter" Drop="LbxAccessLinks_OnDrop" - ItemTemplate="{StaticResource ListViewTemplateAccessLinks}" ItemsSource="{Binding Settings.QuickAccessLinks}" Loaded="lbxAccessLinks_Loaded" - SelectedItem="{Binding SelectedQuickAccessLink}" /> + SelectedItem="{Binding SelectedQuickAccessLink}" + SizeChanged="lbxAccessLinks_SizeChanged"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs new file mode 100644 index 00000000000..36a00e9e59f --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows; +using System.Windows.Forms; +using Flow.Launcher.Plugin.Explorer.Helper; +using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; + +namespace Flow.Launcher.Plugin.Explorer.Views; + +public partial class QuickAccessLinkSettings : INotifyPropertyChanged +{ + private string _selectedPath; + public string SelectedPath + { + get => _selectedPath; + set + { + if (_selectedPath != value) + { + _selectedPath = value; + OnPropertyChanged(); + if (string.IsNullOrEmpty(_selectedName)) + { + SelectedName = _selectedPath.GetPathName(); + } + } + } + } + + private string _selectedName; + public string SelectedName + { + get + { + return string.IsNullOrEmpty(_selectedName) ? _selectedPath.GetPathName() : _selectedName; + } + set + { + if (_selectedName != value) + { + _selectedName = value; + OnPropertyChanged(); + } + } + } + + private bool IsEdit { get; } + private AccessLink SelectedAccessLink { get; } + + public ObservableCollection QuickAccessLinks { get; } + + public QuickAccessLinkSettings(ObservableCollection quickAccessLinks) + { + IsEdit = false; + QuickAccessLinks = quickAccessLinks; + InitializeComponent(); + } + + public QuickAccessLinkSettings(ObservableCollection quickAccessLinks, AccessLink selectedAccessLink) + { + IsEdit = true; + _selectedName = selectedAccessLink.Name; + _selectedPath = selectedAccessLink.Path; + SelectedAccessLink = selectedAccessLink; + QuickAccessLinks = quickAccessLinks; + InitializeComponent(); + } + + private void BtnCancel_OnClick(object sender, RoutedEventArgs e) + { + DialogResult = false; + Close(); + } + + private void OnDoneButtonClick(object sender, RoutedEventArgs e) + { + // Validate the input before proceeding + if (string.IsNullOrEmpty(SelectedName) || string.IsNullOrEmpty(SelectedPath)) + { + var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_no_folder_selected"); + Main.Context.API.ShowMsgBox(warning); + return; + } + + // Check if the path already exists in the quick access links + if (QuickAccessLinks.Any(x => + x.Path.Equals(SelectedPath, StringComparison.OrdinalIgnoreCase) && + x.Name.Equals(SelectedName, StringComparison.OrdinalIgnoreCase))) + { + var warning = Main.Context.API.GetTranslation("plugin_explorer_quick_access_link_path_already_exists"); + Main.Context.API.ShowMsgBox(warning); + return; + } + + // If editing, update the existing link + if (IsEdit) + { + if (SelectedAccessLink == null) return; + + var index = QuickAccessLinks.IndexOf(SelectedAccessLink); + if (index >= 0) + { + var updatedLink = new AccessLink + { + Name = SelectedName, + Type = SelectedAccessLink.Type, + Path = SelectedPath + }; + QuickAccessLinks[index] = updatedLink; + } + DialogResult = true; + Close(); + } + // Otherwise, add a new one + else + { + var newAccessLink = new AccessLink + { + Name = SelectedName, + Path = SelectedPath + }; + QuickAccessLinks.Add(newAccessLink); + DialogResult = true; + Close(); + } + } + + private void SelectPath_OnClick(object commandParameter, RoutedEventArgs e) + { + var folderBrowserDialog = new FolderBrowserDialog(); + + if (folderBrowserDialog.ShowDialog() != System.Windows.Forms.DialogResult.OK) + return; + + SelectedPath = folderBrowserDialog.SelectedPath; + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +}