Merge branch 'dev' into 250331-NewPluginsPage

This commit is contained in:
DB p 2025-04-03 20:08:36 +09:00
commit 378e98f0bb
9 changed files with 124 additions and 93 deletions

View file

@ -1,15 +1,14 @@
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Forms;
using Flow.Launcher.Core.Resource;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
@ -43,8 +42,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal IEnumerable<PluginPair> Setup()
{
// If no plugin is using the language, return empty list
if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase)))
{
return new List<PluginPair>();
}
if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath))
{
@ -56,24 +58,55 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
}
var noRuntimeMessage = string.Format(
InternationalizationManager.Instance.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"),
API.GetTranslation("runtimePluginInstalledChooseRuntimePrompt"),
Language,
EnvName,
Environment.NewLine
);
if (API.ShowMsgBox(noRuntimeMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.No)
{
var msg = string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
string selectedFile;
var msg = string.Format(API.GetTranslation("runtimePluginChooseRuntimeExecutable"), EnvName);
selectedFile = GetFileFromDialog(msg, FileDialogFilter);
var selectedFile = GetFileFromDialog(msg, FileDialogFilter);
if (!string.IsNullOrEmpty(selectedFile))
{
PluginsSettingsFilePath = selectedFile;
}
// Nothing selected because user pressed cancel from the file dialog window
if (string.IsNullOrEmpty(selectedFile))
InstallEnvironment();
else
{
var forceDownloadMessage = string.Format(
API.GetTranslation("runtimeExecutableInvalidChooseDownload"),
Language,
EnvName,
Environment.NewLine
);
// Let users select valid path or choose to download
while (string.IsNullOrEmpty(selectedFile))
{
if (API.ShowMsgBox(forceDownloadMessage, string.Empty, MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
// Continue select file
selectedFile = GetFileFromDialog(msg, FileDialogFilter);
}
else
{
// User selected no, break the loop
break;
}
}
if (!string.IsNullOrEmpty(selectedFile))
{
PluginsSettingsFilePath = selectedFile;
}
else
{
InstallEnvironment();
}
}
}
else
{
@ -86,7 +119,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
}
else
{
API.ShowMsgBox(string.Format(InternationalizationManager.Instance.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
API.ShowMsgBox(string.Format(API.GetTranslation("runtimePluginUnableToSetExecutablePath"), Language));
Log.Error("PluginsLoader",
$"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.",
$"{Language}Environment");
@ -99,13 +132,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath)
{
if (expectedPath == currentPath)
return;
if (expectedPath == currentPath) return;
FilesFolders.RemoveFolderIfExists(installedDirPath, (s) => API.ShowMsgBox(s));
InstallEnvironment();
}
internal abstract PluginPair CreatePluginPair(string filePath, PluginMetadata metadata);
@ -126,7 +157,7 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
return pluginPairs;
}
private string GetFileFromDialog(string title, string filter = "")
private static string GetFileFromDialog(string title, string filter = "")
{
var dlg = new OpenFileDialog
{
@ -140,7 +171,6 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
var result = dlg.ShowDialog();
return result == DialogResult.OK ? dlg.FileName : string.Empty;
}
/// <summary>
@ -183,31 +213,33 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
else
{
if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName))
{
settings.PluginSettings.PythonExecutablePath
= GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath);
}
if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName))
{
settings.PluginSettings.NodeExecutablePath
= GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath);
}
}
}
private static bool IsUsingPortablePath(string filePath, string pluginEnvironmentName)
{
if (string.IsNullOrEmpty(filePath))
return false;
if (string.IsNullOrEmpty(filePath)) return false;
// DataLocation.PortableDataPath returns the current portable path, this determines if an out
// of date path is also a portable path.
var portableAppEnvLocation = $"UserData\\{DataLocation.PluginEnvironments}\\{pluginEnvironmentName}";
var portableAppEnvLocation = Path.Combine("UserData", DataLocation.PluginEnvironments, pluginEnvironmentName);
return filePath.Contains(portableAppEnvLocation);
}
private static bool IsUsingRoamingPath(string filePath)
{
if (string.IsNullOrEmpty(filePath))
return false;
if (string.IsNullOrEmpty(filePath)) return false;
return filePath.StartsWith(DataLocation.RoamingDataPath);
}
@ -217,8 +249,8 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
var index = filePath.IndexOf(DataLocation.PluginEnvironments);
// get the substring after "Environments" because we can not determine it dynamically
var ExecutablePathSubstring = filePath.Substring(index + DataLocation.PluginEnvironments.Count());
return $"{DataLocation.PluginEnvironmentsPath}{ExecutablePathSubstring}";
var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..];
return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}";
}
}
}

View file

@ -1,10 +1,10 @@
using Droplex;
using System.Collections.Generic;
using System.IO;
using Droplex;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
using System.Collections.Generic;
using System.IO;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
@ -22,7 +22,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override string FileDialogFilter => "Python|pythonw.exe";
internal override string PluginsSettingsFilePath { get => PluginSettings.PythonExecutablePath; set => PluginSettings.PythonExecutablePath = value; }
internal override string PluginsSettingsFilePath
{
get => PluginSettings.PythonExecutablePath;
set => PluginSettings.PythonExecutablePath = value;
}
internal PythonEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }

View file

@ -1,10 +1,10 @@
using System.Collections.Generic;
using Droplex;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin;
using System.IO;
using Droplex;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
@ -19,7 +19,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0");
internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe");
internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; }
internal override string PluginsSettingsFilePath
{
get => PluginSettings.NodeExecutablePath;
set => PluginSettings.NodeExecutablePath = value;
}
internal TypeScriptEnvironment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }

View file

@ -1,10 +1,10 @@
using System.Collections.Generic;
using Droplex;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin;
using System.IO;
using Droplex;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using Flow.Launcher.Plugin.SharedCommands;
namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
@ -19,7 +19,11 @@ namespace Flow.Launcher.Core.ExternalPlugins.Environments
internal override string InstallPath => Path.Combine(EnvPath, "Node-v16.18.0");
internal override string ExecutablePath => Path.Combine(InstallPath, "node-v16.18.0-win-x64\\node.exe");
internal override string PluginsSettingsFilePath { get => PluginSettings.NodeExecutablePath; set => PluginSettings.NodeExecutablePath = value; }
internal override string PluginsSettingsFilePath
{
get => PluginSettings.NodeExecutablePath;
set => PluginSettings.NodeExecutablePath = value;
}
internal TypeScriptV2Environment(List<PluginMetadata> pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { }

View file

@ -1,20 +1,19 @@
using Flow.Launcher.Core.ExternalPlugins;
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.DependencyInjection;
using Flow.Launcher.Core.ExternalPlugins;
using Flow.Launcher.Infrastructure;
using Flow.Launcher.Infrastructure.Logger;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin;
using ISavable = Flow.Launcher.Plugin.ISavable;
using Flow.Launcher.Plugin.SharedCommands;
using System.Text.Json;
using Flow.Launcher.Core.Resource;
using CommunityToolkit.Mvvm.DependencyInjection;
using ISavable = Flow.Launcher.Plugin.ISavable;
namespace Flow.Launcher.Core.Plugin
{

View file

@ -138,6 +138,11 @@ namespace Flow.Launcher
{
await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () =>
{
// Because new message box api uses MessageBoxEx window,
// if it is created and closed before main window is created, it will cause the application to exit.
// So set to OnExplicitShutdown to prevent the application from shutting down before main window is created
Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
Log.SetLogLevel(_settings.LogLevel);
Ioc.Default.GetRequiredService<Portable>().PreStartCleanUpAfterPortabilityUpdate();

View file

@ -9,6 +9,11 @@
Click no if it's already installed, and you will be prompted to select the folder that contains the {1} executable
</system:String>
<system:String x:Key="runtimePluginChooseRuntimeExecutable">Please select the {0} executable</system:String>
<system:String x:Key="runtimeExecutableInvalidChooseDownload">
Your selected {0} executable is invalid.
{2}{2}
Click yes if you would like select the {0} executable agian. Click no if you would like to download {1}
</system:String>
<system:String x:Key="runtimePluginUnableToSetExecutablePath">Unable to set {0} executable path, please try from Flow's settings (scroll down to the bottom).</system:String>
<system:String x:Key="failedToInitializePluginsTitle">Fail to Init Plugins</system:String>
<system:String x:Key="failedToInitializePluginsMessage">Plugins: {0} - fail to load and would be disabled, please contact plugin creator for help</system:String>

View file

@ -28,16 +28,16 @@
Style="{StaticResource PageTitle}"
Text="{DynamicResource general}"
TextAlignment="left" />
<cc:CardGroup Margin="0 10 0 0">
<cc:Card Title="{DynamicResource startFlowLauncherOnSystemStartup}" Icon="&#xe8fc;">
<cc:ExCard
Title="{DynamicResource startFlowLauncherOnSystemStartup}"
Margin="0 8 0 0"
Icon="&#xe8fc;">
<cc:ExCard.SideContent>
<ui:ToggleSwitch
IsOn="{Binding StartFlowLauncherOnSystemStartup}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
</cc:ExCard.SideContent>
<cc:Card
Title="{DynamicResource useLogonTaskForStartup}"
Sub="{DynamicResource useLogonTaskForStartupTooltip}"
@ -47,17 +47,16 @@
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:Card
Title="{DynamicResource hideOnStartup}"
Sub="{DynamicResource hideOnStartupToolTip}"
Type="InsideFit">
<ui:ToggleSwitch
IsOn="{Binding Settings.HideOnStartup}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
</cc:CardGroup>
</cc:ExCard>
<cc:Card
Title="{DynamicResource hideOnStartup}"
Icon="&#xed1a;"
Sub="{DynamicResource hideOnStartupToolTip}">
<ui:ToggleSwitch
IsOn="{Binding Settings.HideOnStartup}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:Card Title="{DynamicResource hideFlowLauncherWhenLoseFocus}" Margin="0 14 0 0">
<ui:ToggleSwitch
@ -178,30 +177,6 @@
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:CardGroup Margin="0 14 0 0">
<cc:Card
Title="{DynamicResource searchDelay}"
Icon="&#xE961;"
Sub="{DynamicResource searchDelayToolTip}">
<ui:ToggleSwitch
IsOn="{Binding Settings.SearchQueryResultsWithDelay}"
OffContent="{DynamicResource disable}"
OnContent="{DynamicResource enable}" />
</cc:Card>
<cc:Card
Title="{DynamicResource searchDelayTime}"
Icon="&#xE916;"
Sub="{DynamicResource searchDelayTimeToolTip}">
<ComboBox
MaxWidth="200"
DisplayMemberPath="Display"
ItemsSource="{Binding SearchDelayTimes}"
SelectedItem="{Binding SearchDelayTime}"
SelectedValuePath="Value" />
</cc:Card>
</cc:CardGroup>
<cc:CardGroup Margin="0 14 0 0">
<cc:Card Title="{DynamicResource querySearchPrecision}" Sub="{DynamicResource querySearchPrecisionToolTip}">
<ComboBox

View file

@ -9,9 +9,13 @@ namespace Flow.Launcher.Plugin.Sys
{
public const string Keyword = "fltheme";
private readonly Theme _theme;
private readonly PluginInitContext _context;
// Do not initialize it in the constructor, because it will cause null reference in
// var dicts = Application.Current.Resources.MergedDictionaries; line of Theme
private Theme theme = null;
private Theme Theme => theme ??= Ioc.Default.GetRequiredService<Theme>();
#region Theme Selection
// Theme select codes simplified from SettingsPaneThemeViewModel.cs
@ -19,24 +23,23 @@ namespace Flow.Launcher.Plugin.Sys
private Theme.ThemeData _selectedTheme;
public Theme.ThemeData SelectedTheme
{
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == _theme.GetCurrentTheme());
get => _selectedTheme ??= Themes.Find(v => v.FileNameWithoutExtension == Theme.GetCurrentTheme());
set
{
_selectedTheme = value;
_theme.ChangeTheme(value.FileNameWithoutExtension);
Theme.ChangeTheme(value.FileNameWithoutExtension);
_ = _theme.RefreshFrameAsync();
_ = Theme.RefreshFrameAsync();
}
}
private List<Theme.ThemeData> Themes => _theme.LoadAvailableThemes();
private List<Theme.ThemeData> Themes => Theme.LoadAvailableThemes();
#endregion
public ThemeSelector(PluginInitContext context)
{
_context = context;
_theme = Ioc.Default.GetRequiredService<Theme>();
}
public List<Result> Query(Query query)