From 068133223822baa9731af19cc16e63203f2b8320 Mon Sep 17 00:00:00 2001 From: Shengkai Lin Date: Sat, 31 Jan 2026 23:09:41 +0800 Subject: [PATCH] fix(explorer): complete Avalonia migration and fix action keyword crashes - Remove WPF views (ActionKeywordSetting, ExplorerSettings, PreviewPanel, QuickAccessLinkSettings) - Fix NullReferenceException in ActionKeywordModel by initializing settings - Fix (false,false) crash case in EditActionKeywordAsync - Add null-safe dialog patterns for owner window - Move Focus() to OnOpened for reliable control attachment - Add using for Avalonia PreviewPanel namespace in ResultManager - Update Main.cs to use AvaloniaControl return type --- .../Exceptions/codemap.md | 19 + .../Flow.Launcher.Plugin.Explorer.csproj | 2 +- .../Helper/codemap.md | 19 + Plugins/Flow.Launcher.Plugin.Explorer/Main.cs | 6 +- .../Search/DirectoryInfo/codemap.md | 19 + .../Search/Everything/Exceptions/codemap.md | 19 + .../Search/Everything/codemap.md | 19 + .../Search/IProvider/codemap.md | 19 + .../Search/QuickAccessLinks/codemap.md | 19 + .../Search/ResultManager.cs | 17 +- .../Search/WindowsIndex/codemap.md | 19 + .../Search/codemap.md | 19 + .../ViewModels/SettingsViewModel.cs | 224 +++-- .../ViewModels/codemap.md | 19 + .../Views/ActionKeywordSetting.xaml | 129 --- .../Views/ActionKeywordSetting.xaml.cs | 115 --- .../Views/Avalonia/ActionKeywordSetting.axaml | 2 +- .../Avalonia/ActionKeywordSetting.axaml.cs | 5 + .../Views/Avalonia/PreviewPanel.axaml | 121 +++ .../PreviewPanel.axaml.cs} | 133 ++- .../Views/Avalonia/codemap.md | 19 + .../Views/ExplorerSettings.xaml | 861 ------------------ .../Views/ExplorerSettings.xaml.cs | 139 --- .../Views/PreviewPanel.xaml | 145 --- .../Views/QuickAccessLinkSettings.xaml | 174 ---- .../Views/QuickAccessLinkSettings.xaml.cs | 218 ----- .../Views/codemap.md | 19 + .../Flow.Launcher.Plugin.Explorer/codemap.md | 19 + 28 files changed, 613 insertions(+), 1925 deletions(-) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/codemap.md create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Helper/codemap.md create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/codemap.md create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/codemap.md create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/codemap.md create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/codemap.md create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/codemap.md create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/codemap.md create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Search/codemap.md create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/codemap.md delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml rename Plugins/Flow.Launcher.Plugin.Explorer/Views/{PreviewPanel.xaml.cs => Avalonia/PreviewPanel.axaml.cs} (80%) create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/codemap.md delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml.cs delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml delete mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/Views/codemap.md create mode 100644 Plugins/Flow.Launcher.Plugin.Explorer/codemap.md diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/codemap.md new file mode 100644 index 000000000..cb51ed434 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Exceptions/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj index f290ba3c7..6f643830e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Flow.Launcher.Plugin.Explorer.csproj @@ -8,7 +8,7 @@ true false en - warnings + enable diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Helper/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/codemap.md new file mode 100644 index 000000000..c262d8db4 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Helper/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Helper/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs index 1ca4ea3ec..4571fdc5a 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Main.cs @@ -2,16 +2,15 @@ using Flow.Launcher.Plugin.Explorer.Helper; using Flow.Launcher.Plugin.Explorer.Search; using Flow.Launcher.Plugin.Explorer.Search.Everything; using Flow.Launcher.Plugin.Explorer.ViewModels; -using Flow.Launcher.Plugin.Explorer.Views; using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; -using System.Windows.Controls; using Flow.Launcher.Plugin.Explorer.Exceptions; using System.Linq; using System.Globalization; +using System.Windows.Controls; using AvaloniaControl = Avalonia.Controls.Control; namespace Flow.Launcher.Plugin.Explorer @@ -32,7 +31,7 @@ namespace Flow.Launcher.Plugin.Explorer public Control CreateSettingPanel() { - return new ExplorerSettings(viewModel); + throw new NotSupportedException("WPF settings are no longer supported. Use Avalonia version instead."); } public AvaloniaControl CreateSettingPanelAvalonia() @@ -45,6 +44,7 @@ namespace Flow.Launcher.Plugin.Explorer Context = context; Settings = context.API.LoadSettingJsonStorage(); + ActionKeywordModel.Init(Settings); FillQuickAccessLinkNames(); viewModel = new SettingsViewModel(context, Settings); diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/codemap.md new file mode 100644 index 000000000..7e2379493 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Search/DirectoryInfo/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/codemap.md new file mode 100644 index 000000000..acfd3365f --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/Exceptions/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/codemap.md new file mode 100644 index 000000000..0fe828bae --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Search/Everything/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/codemap.md new file mode 100644 index 000000000..da9327288 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Search/IProvider/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/codemap.md new file mode 100644 index 000000000..c163b125d --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Search/QuickAccessLinks/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index 18eb168b9..38590bda3 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -1,11 +1,10 @@ -using System; +using System; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows.Controls; using System.Windows.Input; using Flow.Launcher.Plugin.Explorer.Search.Everything; -using Flow.Launcher.Plugin.Explorer.Views; using Flow.Launcher.Plugin.SharedCommands; using Flow.Launcher.Plugin.SharedModels; using Peter; @@ -106,7 +105,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search { FilePath = path, }, - PreviewPanel = new Lazy(() => new PreviewPanel(Settings, path, ResultType.Folder)), Action = c => { if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt) @@ -305,7 +303,6 @@ namespace Flow.Launcher.Plugin.Explorer.Search TitleHighlightData = Context.API.FuzzySearch(query.Search, title).MatchData, Score = score, CopyText = filePath, - PreviewPanel = new Lazy(() => new PreviewPanel(Settings, filePath, ResultType.File)), Action = c => { if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt) @@ -371,9 +368,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search { try { - var fileSize = PreviewPanel.GetFileSize(filePath); - var fileCreatedAt = PreviewPanel.GetFileCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); - var fileModifiedAt = PreviewPanel.GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); + var fileSize = Views.Avalonia.PreviewPanel.GetFileSize(filePath); + var fileCreatedAt = Views.Avalonia.PreviewPanel.GetFileCreatedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); + var fileModifiedAt = Views.Avalonia.PreviewPanel.GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); return Localize.plugin_explorer_plugin_tooltip_more_info(filePath, fileSize, fileCreatedAt, fileModifiedAt, Environment.NewLine); } catch (Exception e) @@ -387,9 +384,9 @@ namespace Flow.Launcher.Plugin.Explorer.Search { try { - var folderSize = PreviewPanel.GetFolderSize(folderPath); - var folderCreatedAt = PreviewPanel.GetFolderCreatedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); - var folderModifiedAt = PreviewPanel.GetFolderLastModifiedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); + var folderSize = Views.Avalonia.PreviewPanel.GetFolderSize(folderPath); + var folderCreatedAt = Views.Avalonia.PreviewPanel.GetFolderCreatedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); + var folderModifiedAt = Views.Avalonia.PreviewPanel.GetFolderLastModifiedAt(folderPath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); return Localize.plugin_explorer_plugin_tooltip_more_info(folderPath, folderSize, folderCreatedAt, folderModifiedAt, Environment.NewLine); } catch (Exception e) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/codemap.md new file mode 100644 index 000000000..ed7a11978 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Search/WindowsIndex/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Search/codemap.md new file mode 100644 index 000000000..d0c67afe1 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Search/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 10b3af170..aa10f1a5b 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -8,26 +8,36 @@ using System.IO; using System.Linq; using System.Threading.Tasks; 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; using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; -using Flow.Launcher.Plugin.Explorer.Views; +using Flow.Launcher.Plugin.Explorer.Views.Avalonia; using AvaloniaApp = Avalonia.Application; -using AvaloniaQuickAccessLinkSettings = Flow.Launcher.Plugin.Explorer.Views.Avalonia.QuickAccessLinkSettings; namespace Flow.Launcher.Plugin.Explorer.ViewModels { public partial class SettingsViewModel : BaseModel { /// - /// Detects if we're running in an Avalonia application context + /// Gets the current active Avalonia window to use as dialog owner /// - private static bool IsAvalonia => AvaloniaApp.Current != null; - + private static global::Avalonia.Controls.Window? GetAvaloniaOwnerWindow() + { + if (AvaloniaApp.Current?.ApplicationLifetime is not global::Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop) + return null; + + // First try to find an active window + var activeWindow = desktop.Windows.FirstOrDefault(w => w.IsActive); + if (activeWindow != null) + return activeWindow; + + // Fall back to main window + return desktop.MainWindow; + } + public Settings Settings { get; set; } internal PluginInitContext Context { get; set; } @@ -41,6 +51,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels Context = context; Settings = settings; + ActionKeywordModel.Init(settings); InitializeEngineSelection(); InitializeActionKeywordModels(); } @@ -162,7 +173,6 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels Settings.ShowCreatedDateInPreviewPanel = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowPreviewPanelDateTimeChoices)); - OnPropertyChanged(nameof(PreviewPanelDateTimeChoicesVisibility)); } } @@ -174,7 +184,6 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels Settings.ShowModifiedDateInPreviewPanel = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowPreviewPanelDateTimeChoices)); - OnPropertyChanged(nameof(PreviewPanelDateTimeChoicesVisibility)); } } @@ -186,7 +195,6 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels Settings.ShowFileAgeInPreviewPanel = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowPreviewPanelDateTimeChoices)); - OnPropertyChanged(nameof(PreviewPanelDateTimeChoicesVisibility)); } } @@ -217,9 +225,6 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels public bool ShowPreviewPanelDateTimeChoices => ShowCreatedDateInPreviewPanel || ShowModifiedDateInPreviewPanel; - public Visibility PreviewPanelDateTimeChoicesVisibility => ShowCreatedDateInPreviewPanel || ShowModifiedDateInPreviewPanel ? Visibility.Visible : Visibility.Collapsed; - - public List TimeFormatList { get; } = new() { "h:mm", @@ -296,7 +301,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels public ActionKeywordModel? SelectedActionKeyword { get; set; } [RelayCommand] - private void EditActionKeyword(object obj) + private async Task EditActionKeywordAsync(object obj) { if (SelectedActionKeyword is not { } actionKeyword) { @@ -304,14 +309,32 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels return; } - var actionKeywordWindow = new ActionKeywordSetting(actionKeyword); + var dialog = new ActionKeywordSetting(actionKeyword); + var ownerWindow = GetAvaloniaOwnerWindow(); + bool dialogResult; + if (ownerWindow != null) + { + dialogResult = await dialog.ShowDialog(ownerWindow) ?? false; + } + else + { + // Fallback: show as normal window if owner is not available + dialog.Show(); + var tcs = new TaskCompletionSource(); + dialog.Closed += (_, _) => tcs.TrySetResult(true); + await tcs.Task; + dialogResult = true; + } - if (!(actionKeywordWindow.ShowDialog() ?? false)) + if (!dialogResult) { return; } - switch (actionKeyword.Enabled, actionKeywordWindow.KeywordEnabled) + var newKeyword = dialog.ActionKeyword; + var newEnabled = dialog.KeywordEnabled; + + switch (actionKeyword.Enabled, newEnabled) { case (true, false): Context.API.RemoveActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); @@ -319,18 +342,16 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels case (true, true): // same keyword will have dialog result false Context.API.RemoveActionKeyword(Context.CurrentPluginMetadata.ID, actionKeyword.Keyword); - Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeywordWindow.ActionKeyword); + Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, newKeyword); break; case (false, true): - Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, actionKeywordWindow.ActionKeyword); + Context.API.AddActionKeyword(Context.CurrentPluginMetadata.ID, newKeyword); break; case (false, false): - throw new ArgumentException( - $"Both false in {nameof(actionKeyword)}.{nameof(actionKeyword.Enabled)} and {nameof(actionKeywordWindow)}.{nameof(actionKeywordWindow.KeywordEnabled)} should suggest that the ShowDialog() result is false"); + break; } - (actionKeyword.Keyword, actionKeyword.Enabled) = (actionKeywordWindow.ActionKeyword, actionKeywordWindow.KeywordEnabled); - + (actionKeyword.Keyword, actionKeyword.Enabled) = (newKeyword, newEnabled); } #endregion @@ -352,7 +373,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels } [RelayCommand] - private void EditIndexSearchExcludePaths() + private async Task EditIndexSearchExcludePathsAsync() { var selectedLink = SelectedIndexSearchExcludedPath; var collection = Settings.IndexSearchExcludedSubdirectoryPaths; @@ -363,7 +384,7 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels return; } - var path = PromptUserSelectPath(selectedLink.Type, + var path = await PromptUserSelectPathAsync(selectedLink.Type, selectedLink.Type == ResultType.Folder ? selectedLink.Path : Path.GetDirectoryName(selectedLink.Path)); @@ -380,21 +401,21 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels } [RelayCommand] - private void AddIndexSearchExcludePaths() + private async Task AddIndexSearchExcludePathsAsync() { var container = Settings.IndexSearchExcludedSubdirectoryPaths; if (container is null) return; - - var folderBrowserDialog = new FolderBrowserDialog(); - if (folderBrowserDialog.ShowDialog() != DialogResult.OK) + var path = await PromptUserSelectFolderAsync(); + + if (path is null) return; var newAccessLink = new AccessLink { - Name = folderBrowserDialog.SelectedPath.GetPathName(), - Path = folderBrowserDialog.SelectedPath + Name = path.GetPathName(), + Path = path }; container.Add(newAccessLink); @@ -413,50 +434,52 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels return; } - if (IsAvalonia) + var dialog = new QuickAccessLinkSettings(collection, selectedLink); + var ownerWindow = GetAvaloniaOwnerWindow(); + bool dialogResult; + if (ownerWindow != null) { - var dialog = new AvaloniaQuickAccessLinkSettings(collection, selectedLink); - var mainWindow = AvaloniaApp.Current?.ApplicationLifetime is global::Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop - ? desktop.MainWindow - : null; - var result = await dialog.ShowDialog(mainWindow!); - if (result == true) - { - Save(); - } + dialogResult = await dialog.ShowDialog(ownerWindow) ?? false; } else { - var quickAccessLinkSettings = new QuickAccessLinkSettings(collection, SelectedQuickAccessLink); - if (quickAccessLinkSettings.ShowDialog() == true) - { - Save(); - } + // Fallback: show as normal window if owner is not available + dialog.Show(); + var tcs = new TaskCompletionSource(); + dialog.Closed += (_, _) => tcs.TrySetResult(true); + await tcs.Task; + dialogResult = true; + } + + if (dialogResult) + { + Save(); } } - + [RelayCommand] private async Task AddQuickAccessLinkAsync() { - if (IsAvalonia) + var dialog = new QuickAccessLinkSettings(Settings.QuickAccessLinks); + var ownerWindow = GetAvaloniaOwnerWindow(); + bool dialogResult; + if (ownerWindow != null) { - var dialog = new AvaloniaQuickAccessLinkSettings(Settings.QuickAccessLinks); - var mainWindow = AvaloniaApp.Current?.ApplicationLifetime is global::Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop - ? desktop.MainWindow - : null; - var result = await dialog.ShowDialog(mainWindow!); - if (result == true) - { - Save(); - } + dialogResult = await dialog.ShowDialog(ownerWindow) ?? false; } else { - var quickAccessLinkSettings = new QuickAccessLinkSettings(Settings.QuickAccessLinks); - if (quickAccessLinkSettings.ShowDialog() == true) - { - Save(); - } + // Fallback: show as normal window if owner is not available + dialog.Show(); + var tcs = new TaskCompletionSource(); + dialog.Closed += (_, _) => tcs.TrySetResult(true); + await tcs.Task; + dialogResult = true; + } + + if (dialogResult) + { + Save(); } } @@ -492,45 +515,60 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels } Save(); } - + private void ShowUnselectedMessage() { var warning = Localize.plugin_explorer_make_selection_warning(); Context.API.ShowMsgBox(warning); } - #endregion - - private static string? PromptUserSelectPath(ResultType type, string? initialDirectory = null) + private static async Task PromptUserSelectPathAsync(ResultType type, string? initialDirectory = null) { string? path = null; if (type is ResultType.Folder) { - var folderBrowserDialog = new FolderBrowserDialog(); - - if (initialDirectory is not null) - folderBrowserDialog.InitialDirectory = initialDirectory; - - if (folderBrowserDialog.ShowDialog() != DialogResult.OK) - return path; - - path = folderBrowserDialog.SelectedPath; + path = await PromptUserSelectFolderAsync(initialDirectory); } else if (type is ResultType.File) { - var openFileDialog = new OpenFileDialog(); - if (initialDirectory is not null) - openFileDialog.InitialDirectory = initialDirectory; - - if (openFileDialog.ShowDialog() != DialogResult.OK) - return path; - - path = openFileDialog.FileName; + path = await PromptUserSelectFileAsync(initialDirectory); } return path; } + private static async Task PromptUserSelectFolderAsync(string? initialDirectory = null) + { + var mainWindow = AvaloniaApp.Current?.ApplicationLifetime is global::Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop + ? desktop.MainWindow + : null; + + if (mainWindow == null) return null; + + var folders = await mainWindow.StorageProvider.OpenFolderPickerAsync(new global::Avalonia.Platform.Storage.FolderPickerOpenOptions + { + AllowMultiple = false + }); + + return folders.Count > 0 ? folders[0].Path.LocalPath : null; + } + + private static async Task PromptUserSelectFileAsync(string? initialDirectory = null) + { + var mainWindow = AvaloniaApp.Current?.ApplicationLifetime is global::Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop + ? desktop.MainWindow + : null; + + if (mainWindow == null) return null; + + var files = await mainWindow.StorageProvider.OpenFilePickerAsync(new global::Avalonia.Platform.Storage.FilePickerOpenOptions + { + AllowMultiple = false + }); + + return files.Count > 0 ? files[0].Path.LocalPath : null; + } + internal static void OpenWindowsIndexingOptions() { var psi = new ProcessStartInfo @@ -544,9 +582,9 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels } [RelayCommand] - private void OpenFileEditorPath() + private async Task OpenFileEditorPathAsync() { - var path = PromptUserSelectPath(ResultType.File, Settings.EditorPath != null ? Path.GetDirectoryName(Settings.EditorPath) : null); + var path = await PromptUserSelectFileAsync(Settings.EditorPath != null ? Path.GetDirectoryName(Settings.EditorPath) : null); if (path is null) return; @@ -554,9 +592,9 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels } [RelayCommand] - private void OpenFolderEditorPath() + private async Task OpenFolderEditorPathAsync() { - var path = PromptUserSelectPath(ResultType.File, Settings.FolderEditorPath != null ? Path.GetDirectoryName(Settings.FolderEditorPath) : null); + var path = await PromptUserSelectFolderAsync(Settings.FolderEditorPath != null ? Path.GetDirectoryName(Settings.FolderEditorPath) : null); if (path is null) return; @@ -564,9 +602,9 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels } [RelayCommand] - private void OpenShellPath() + private async Task OpenShellPathAsync() { - var path = PromptUserSelectPath(ResultType.File, Settings.EditorPath != null ? Path.GetDirectoryName(Settings.EditorPath) : null); + var path = await PromptUserSelectFileAsync(Settings.EditorPath != null ? Path.GetDirectoryName(Settings.EditorPath) : null); if (path is null) return; @@ -628,6 +666,8 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels } } + #endregion + #region Everything FastSortWarning public List AllEverythingSortOptions { get; } = EverythingSortOptionLocalized.GetValues(); @@ -646,23 +686,23 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels } } - public Visibility FastSortWarningVisibility + public bool FastSortWarningVisibility { get { try { - return EverythingApi.IsFastSortOption(Settings.SortOption) ? Visibility.Collapsed : Visibility.Visible; + return !EverythingApi.IsFastSortOption(Settings.SortOption); } catch (IPCErrorException) { // this error occurs if the Everything service is not running, in this instance show the warning and // update the message to let user know in the settings panel. - return Visibility.Visible; + return true; } catch (DllNotFoundException) { - return Visibility.Collapsed; + return false; } } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/codemap.md new file mode 100644 index 000000000..f55400af2 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml deleted file mode 100644 index ae0f91d93..000000000 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs deleted file mode 100644 index 562170062..000000000 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ActionKeywordSetting.xaml.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.Linq; -using System.Windows; -using System.Windows.Input; -using CommunityToolkit.Mvvm.ComponentModel; -using Flow.Launcher.Plugin.Explorer.ViewModels; - -namespace Flow.Launcher.Plugin.Explorer.Views -{ - [INotifyPropertyChanged] - public partial class ActionKeywordSetting - { - private ActionKeywordModel CurrentActionKeyword { get; } - - public string ActionKeyword - { - get => actionKeyword; - set - { - // Set Enable to be true if user change ActionKeyword - KeywordEnabled = true; - _ = SetProperty(ref actionKeyword, value); - } - } - - public bool KeywordEnabled - { - get => _keywordEnabled; - set => _ = SetProperty(ref _keywordEnabled, value); - } - - private string actionKeyword; - private bool _keywordEnabled; - - public ActionKeywordSetting(ActionKeywordModel selectedActionKeyword) - { - CurrentActionKeyword = selectedActionKeyword; - ActionKeyword = selectedActionKeyword.Keyword; - KeywordEnabled = selectedActionKeyword.Enabled; - - InitializeComponent(); - - TxtCurrentActionKeyword.Focus(); - } - - private void OnDoneButtonClick(object sender, RoutedEventArgs e) - { - if (string.IsNullOrEmpty(ActionKeyword)) - ActionKeyword = Query.GlobalPluginWildcardSign; - - if (CurrentActionKeyword.Keyword == ActionKeyword && CurrentActionKeyword.Enabled == KeywordEnabled) - { - DialogResult = false; - Close(); - return; - } - - if (ActionKeyword == Query.GlobalPluginWildcardSign) - switch (CurrentActionKeyword.KeywordProperty, KeywordEnabled) - { - case (Settings.ActionKeyword.FileContentSearchActionKeyword, true): - Main.Context.API.ShowMsgBox(Localize.plugin_explorer_globalActionKeywordInvalid()); - return; - case (Settings.ActionKeyword.QuickAccessActionKeyword, true): - Main.Context.API.ShowMsgBox(Localize.plugin_explorer_quickaccess_globalActionKeywordInvalid()); - return; - } - - if (!KeywordEnabled || !Main.Context.API.ActionKeywordAssigned(ActionKeyword)) - { - DialogResult = true; - Close(); - return; - } - - // The keyword is not valid, so show message - Main.Context.API.ShowMsgBox(Localize.plugin_explorer_new_action_keyword_assigned()); - } - - private void BtnCancel_OnClick(object sender, RoutedEventArgs e) - { - DialogResult = false; - Close(); - } - - private void TxtCurrentActionKeyword_OnKeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.Enter) - { - DownButton.Focus(); - OnDoneButtonClick(sender, e); - e.Handled = true; - } - if (e.Key == Key.Space) - { - e.Handled = true; - } - } - - private void TextBox_Pasting(object sender, DataObjectPastingEventArgs e) - { - if (e.DataObject.GetDataPresent(DataFormats.Text)) - { - string text = e.DataObject.GetData(DataFormats.Text) as string; - if (!string.IsNullOrEmpty(text) && text.Any(char.IsWhiteSpace)) - { - e.CancelCommand(); - } - } - else - { - e.CancelCommand(); - } - } - } -} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml index 7aad85fcf..1d646107c 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml @@ -7,7 +7,7 @@ Title="{DynamicResource plugin_explorer_manageactionkeywords_header}" Width="400" Height="255" - WindowStartupLocation="CenterOwner" + WindowStartupLocation="CenterScreen" CanResize="False"> diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs index 16d84778d..4f815d71e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ActionKeywordSetting.axaml.cs @@ -1,4 +1,5 @@ #nullable enable +using System; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; @@ -49,7 +50,11 @@ public partial class ActionKeywordSetting : Window, INotifyPropertyChanged InitializeComponent(); DataContext = this; + } + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); this.FindControl("TxtCurrentActionKeyword")?.Focus(); } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml new file mode 100644 index 000000000..353e02e9b --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml.cs similarity index 80% rename from Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs rename to Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml.cs index 3c627cc06..55d99b37f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/PreviewPanel.axaml.cs @@ -1,59 +1,93 @@ -using System; +#nullable enable +using System; using System.ComponentModel; using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Media.Imaging; +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Imaging; using Flow.Launcher.Plugin.Explorer.Search; -using CommunityToolkit.Mvvm.ComponentModel; -namespace Flow.Launcher.Plugin.Explorer.Views; +namespace Flow.Launcher.Plugin.Explorer.Views.Avalonia; -#nullable enable - -[INotifyPropertyChanged] -public partial class PreviewPanel : UserControl +public partial class PreviewPanel : UserControl, INotifyPropertyChanged { private static readonly string ClassName = nameof(PreviewPanel); public string FilePath { get; } public string FileName { get; } - [ObservableProperty] private string _fileSize = Localize.plugin_explorer_plugin_tooltip_more_info_unknown(); - - [ObservableProperty] private string _createdAt = ""; - - [ObservableProperty] private string _lastModifiedAt = ""; + private IImage? _previewImage; - [ObservableProperty] - private ImageSource _previewImage = new BitmapImage(); + public string FileSize + { + get => _fileSize; + set + { + if (_fileSize != value) + { + _fileSize = value; + OnPropertyChanged(); + } + } + } + + public string CreatedAt + { + get => _createdAt; + set + { + if (_createdAt != value) + { + _createdAt = value; + OnPropertyChanged(); + } + } + } + + public string LastModifiedAt + { + get => _lastModifiedAt; + set + { + if (_lastModifiedAt != value) + { + _lastModifiedAt = value; + OnPropertyChanged(); + } + } + } + + public IImage? PreviewImage + { + get => _previewImage; + set + { + if (_previewImage != value) + { + _previewImage = value; + OnPropertyChanged(); + } + } + } private Settings Settings { get; } - public Visibility FileSizeVisibility => Settings.ShowFileSizeInPreviewPanel - ? Visibility.Visible - : Visibility.Collapsed; - public Visibility CreatedAtVisibility => Settings.ShowCreatedDateInPreviewPanel - ? Visibility.Visible - : Visibility.Collapsed; - public Visibility LastModifiedAtVisibility => Settings.ShowModifiedDateInPreviewPanel - ? Visibility.Visible - : Visibility.Collapsed; + public bool FileSizeVisibility => Settings.ShowFileSizeInPreviewPanel; + public bool CreatedAtVisibility => Settings.ShowCreatedDateInPreviewPanel; + public bool LastModifiedAtVisibility => Settings.ShowModifiedDateInPreviewPanel; - public Visibility FileInfoVisibility => + public bool FileInfoVisibility => Settings.ShowFileSizeInPreviewPanel || Settings.ShowCreatedDateInPreviewPanel || - Settings.ShowModifiedDateInPreviewPanel - ? Visibility.Visible - : Visibility.Collapsed; + Settings.ShowModifiedDateInPreviewPanel; public PreviewPanel(Settings settings, string filePath, ResultType type) { @@ -62,6 +96,7 @@ public partial class PreviewPanel : UserControl FileName = Path.GetFileName(filePath); InitializeComponent(); + DataContext = this; if (Settings.ShowFileSizeInPreviewPanel) { @@ -73,8 +108,8 @@ public partial class PreviewPanel : UserControl { _ = Task.Run(() => { - FileSize = GetFolderSize(filePath); - OnPropertyChanged(nameof(FileSize)); + var size = GetFolderSize(filePath); + global::Avalonia.Threading.Dispatcher.UIThread.Post(() => FileSize = size); }).ConfigureAwait(false); } } @@ -88,7 +123,7 @@ public partial class PreviewPanel : UserControl if (Settings.ShowModifiedDateInPreviewPanel) { - LastModifiedAt = type == ResultType.File ? + LastModifiedAt = type == ResultType.File ? GetFileLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel) : GetFolderLastModifiedAt(filePath, Settings.PreviewPanelDateFormat, Settings.PreviewPanelTimeFormat, Settings.ShowFileAgeInPreviewPanel); } @@ -98,7 +133,24 @@ public partial class PreviewPanel : UserControl private async Task LoadImageAsync() { - PreviewImage = await Main.Context.API.LoadImageAsync(FilePath, true).ConfigureAwait(false); + try + { + var imagePath = FilePath; + if (string.IsNullOrEmpty(imagePath) || !File.Exists(imagePath)) + return; + + var bitmap = new Bitmap(imagePath); + global::Avalonia.Threading.Dispatcher.UIThread.Post(() => PreviewImage = bitmap); + } + catch (Exception e) + { + Main.Context.API.LogException(ClassName, $"Failed to load image for {FilePath}", e); + } + } + + private void InitializeComponent() + { + global::Avalonia.Markup.Xaml.AvaloniaXamlLoader.Load(this); } public static string GetFileSize(string filePath) @@ -323,8 +375,15 @@ public partial class PreviewPanel : UserControl var yearsDiff = now.Year - fileDateTime.Year; if (now.Month < fileDateTime.Month || (now.Month == fileDateTime.Month && now.Day < fileDateTime.Day)) - yearsDiff--; + yearsDiff--; - return yearsDiff == 1 ? Localize.OneYearAgo(): Localize.YearsAgo(yearsDiff); + return yearsDiff == 1 ? Localize.OneYearAgo() : Localize.YearsAgo(yearsDiff); + } + + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/codemap.md new file mode 100644 index 000000000..8c892edfe --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Views/Avalonia/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml deleted file mode 100644 index 08abc3ba6..000000000 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ /dev/null @@ -1,861 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs deleted file mode 100644 index f8929549b..000000000 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/QuickAccessLinkSettings.xaml.cs +++ /dev/null @@ -1,218 +0,0 @@ -using System; -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.IO; -using System.Linq; -using System.Windows; -using System.Windows.Forms; -using Flow.Launcher.Plugin.Explorer.Helper; -using Flow.Launcher.Plugin.Explorer.Search; -using Flow.Launcher.Plugin.Explorer.Search.QuickAccessLinks; -using CommunityToolkit.Mvvm.ComponentModel; - -namespace Flow.Launcher.Plugin.Explorer.Views; - -[INotifyPropertyChanged] -public partial class QuickAccessLinkSettings -{ - private static readonly string ClassName = nameof(QuickAccessLinkSettings); - - private string _selectedPath; - public string SelectedPath - { - get => _selectedPath; - set - { - if (_selectedPath != value) - { - _selectedPath = value; - OnPropertyChanged(); - if (string.IsNullOrEmpty(_selectedName)) - { - SelectedName = _selectedPath.GetPathName(); - } - if (!string.IsNullOrEmpty(_selectedPath)) - { - _accessLinkType = GetResultType(_selectedPath); - } - } - } - } - - private string _selectedName; - public string SelectedName - { - get - { - return string.IsNullOrEmpty(_selectedName) ? _selectedPath.GetPathName() : _selectedName; - } - set - { - if (_selectedName != value) - { - _selectedName = value; - OnPropertyChanged(); - } - } - } - - public bool IsFileSelected { get; set; } - public bool IsFolderSelected { get; set; } = true; // Default to Folder - - private bool IsEdit { get; } - private AccessLink SelectedAccessLink { get; } - - public ObservableCollection QuickAccessLinks { get; } - - private ResultType _accessLinkType = ResultType.Folder; // Default to Folder - - public QuickAccessLinkSettings(ObservableCollection quickAccessLinks) - { - IsEdit = false; - QuickAccessLinks = quickAccessLinks; - InitializeComponent(); - } - - public QuickAccessLinkSettings(ObservableCollection quickAccessLinks, AccessLink selectedAccessLink) - { - IsEdit = true; - _selectedName = selectedAccessLink.Name; - _selectedPath = selectedAccessLink.Path; - _accessLinkType = GetResultType(_selectedPath); // Initialize link type - IsFileSelected = selectedAccessLink.Type == ResultType.File; // Initialize default selection - IsFolderSelected = !IsFileSelected; - 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 = Localize.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 = Localize.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) - { - var index = QuickAccessLinks.IndexOf(SelectedAccessLink); - if (index >= 0) - { - var updatedLink = new AccessLink - { - Name = SelectedName, - Type = _accessLinkType, - Path = SelectedPath - }; - QuickAccessLinks[index] = updatedLink; - } - DialogResult = true; - Close(); - } - // Add a new one if the selected access link is null (should not happen in edit mode, but just in case) - else - { - AddNewAccessLink(); - } - } - // Otherwise, add a new one - else - { - AddNewAccessLink(); - } - - void AddNewAccessLink() - { - var newAccessLink = new AccessLink - { - Name = SelectedName, - Type = _accessLinkType, - Path = SelectedPath - }; - QuickAccessLinks.Add(newAccessLink); - DialogResult = true; - Close(); - } - } - - private void SelectPath_OnClick(object commandParameter, RoutedEventArgs e) - { - // Open file or folder selection dialog based on the selected radio button - if (IsFileSelected) - { - var openFileDialog = new OpenFileDialog - { - Multiselect = false, - CheckFileExists = true, - CheckPathExists = true - }; - - if (openFileDialog.ShowDialog() != System.Windows.Forms.DialogResult.OK || - string.IsNullOrEmpty(openFileDialog.FileName)) - return; - - SelectedPath = openFileDialog.FileName; - } - else // Folder selection - { - var folderBrowserDialog = new FolderBrowserDialog - { - ShowNewFolderButton = true - }; - - if (folderBrowserDialog.ShowDialog() != System.Windows.Forms.DialogResult.OK || - string.IsNullOrEmpty(folderBrowserDialog.SelectedPath)) - return; - - SelectedPath = folderBrowserDialog.SelectedPath; - } - } - - private static ResultType GetResultType(string path) - { - // Check if the path is a file or folder - if (File.Exists(path)) - { - return ResultType.File; - } - else if (Directory.Exists(path)) - { - if (string.Equals(Path.GetPathRoot(path), path, StringComparison.OrdinalIgnoreCase)) - { - return ResultType.Volume; - } - else - { - return ResultType.Folder; - } - } - else - { - // This should not happen, but just in case, we assume it's a folder - Main.Context.API.LogError(ClassName, $"The path '{path}' does not exist or is invalid. Defaulting to Folder type."); - return ResultType.Folder; - } - } -} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/Views/codemap.md new file mode 100644 index 000000000..422e403f6 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/Views/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/codemap.md b/Plugins/Flow.Launcher.Plugin.Explorer/codemap.md new file mode 100644 index 000000000..691d38506 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/codemap.md @@ -0,0 +1,19 @@ +# Plugins/Flow.Launcher.Plugin.Explorer/ + + + +## Responsibility + + + +## Design + + + +## Flow + + + +## Integration + +