diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs new file mode 100644 index 000000000..9ebacc942 --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/AbstractPluginEnvironment.cs @@ -0,0 +1,234 @@ +using Flow.Launcher.Infrastructure.Logger; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; +using Flow.Launcher.Plugin.SharedCommands; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Windows.Forms; + +namespace Flow.Launcher.Core.ExternalPlugins.Environments +{ + public abstract class AbstractPluginEnvironment + { + internal abstract string Language { get; } + + internal abstract string EnvName { get; } + + internal abstract string EnvPath { get; } + + internal abstract string InstallPath { get; } + + internal abstract string ExecutablePath { get; } + + internal virtual string FileDialogFilter => string.Empty; + + internal abstract string PluginsSettingsFilePath { get; set; } + + internal List PluginMetadataList; + + internal PluginsSettings PluginSettings; + + internal AbstractPluginEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) + { + PluginMetadataList = pluginMetadataList; + PluginSettings = pluginSettings; + } + + internal IEnumerable Setup() + { + if (!PluginMetadataList.Any(o => o.Language.Equals(Language, StringComparison.OrdinalIgnoreCase))) + return new List(); + + // TODO: Remove. This is backwards compatibility for 1.10.0 release- changed PythonEmbeded to Environments/Python + if (Language.Equals(AllowedLanguage.Python, StringComparison.OrdinalIgnoreCase)) + { + FilesFolders.RemoveFolderIfExists(Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable")); + + if (!string.IsNullOrEmpty(PluginSettings.PythonDirectory) && PluginSettings.PythonDirectory.StartsWith(Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable"))) + { + InstallEnvironment(); + PluginSettings.PythonDirectory = string.Empty; + } + } + + if (!string.IsNullOrEmpty(PluginsSettingsFilePath) && FilesFolders.FileExists(PluginsSettingsFilePath)) + { + // Ensure latest only if user is using Flow's environment setup. + if (PluginsSettingsFilePath.StartsWith(EnvPath, StringComparison.OrdinalIgnoreCase)) + EnsureLatestInstalled(ExecutablePath, PluginsSettingsFilePath, EnvPath); + + return SetPathForPluginPairs(PluginsSettingsFilePath, Language); + } + + if (MessageBox.Show($"Flow detected you have installed {Language} plugins, which " + + $"will require {EnvName} to run. Would you like to download {EnvName}? " + + Environment.NewLine + Environment.NewLine + + "Click no if it's already installed, " + + $"and you will be prompted to select the folder that contains the {EnvName} executable", + string.Empty, MessageBoxButtons.YesNo) == DialogResult.No) + { + var msg = $"Please select the {EnvName} executable"; + var selectedFile = string.Empty; + + 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 + { + InstallEnvironment(); + } + + if (FilesFolders.FileExists(PluginsSettingsFilePath)) + { + return SetPathForPluginPairs(PluginsSettingsFilePath, Language); + } + else + { + MessageBox.Show( + $"Unable to set {Language} executable path, please try from Flow's settings (scroll down to the bottom)."); + Log.Error("PluginsLoader", + $"Not able to successfully set {EnvName} path, setting's plugin executable path variable is still an empty string.", + $"{Language}Environment"); + + return new List(); + } + } + + internal abstract void InstallEnvironment(); + + private void EnsureLatestInstalled(string expectedPath, string currentPath, string installedDirPath) + { + if (expectedPath == currentPath) + return; + + FilesFolders.RemoveFolderIfExists(installedDirPath); + + InstallEnvironment(); + + } + + internal abstract PluginPair CreatePluginPair(string filePath, PluginMetadata metadata); + + private IEnumerable SetPathForPluginPairs(string filePath, string languageToSet) + { + var pluginPairs = new List(); + + foreach (var metadata in PluginMetadataList) + { + if (metadata.Language.Equals(languageToSet, StringComparison.OrdinalIgnoreCase)) + pluginPairs.Add(CreatePluginPair(filePath, metadata)); + } + + return pluginPairs; + } + + private string GetFileFromDialog(string title, string filter = "") + { + var dlg = new OpenFileDialog + { + InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), + Multiselect = false, + CheckFileExists = true, + CheckPathExists = true, + Title = title, + Filter = filter + }; + + var result = dlg.ShowDialog(); + if (result == DialogResult.OK) + { + return dlg.FileName; + } + else + { + return string.Empty; + } + } + + /// + /// After app updated while in portable mode or switched between portable/roaming mode, + /// need to update each plugin's executable path so user will not be prompted again to reinstall the environments. + /// + /// + public static void PreStartPluginExecutablePathUpdate(Settings settings) + { + if (DataLocation.PortableDataLocationInUse()) + { + // When user is using portable but has moved flow to a different location + if (IsUsingPortablePath(settings.PluginSettings.PythonExecutablePath, DataLocation.PythonEnvironmentName) + && !settings.PluginSettings.PythonExecutablePath.StartsWith(DataLocation.PortableDataPath)) + { + settings.PluginSettings.PythonExecutablePath + = GetUpdatedEnvironmentPath(settings.PluginSettings.PythonExecutablePath); + } + + if (IsUsingPortablePath(settings.PluginSettings.NodeExecutablePath, DataLocation.NodeEnvironmentName) + && !settings.PluginSettings.NodeExecutablePath.StartsWith(DataLocation.PortableDataPath)) + { + settings.PluginSettings.NodeExecutablePath + = GetUpdatedEnvironmentPath(settings.PluginSettings.NodeExecutablePath); + } + + // When user has switched from roaming to portable + if (IsUsingRoamingPath(settings.PluginSettings.PythonExecutablePath)) + { + settings.PluginSettings.PythonExecutablePath + = settings.PluginSettings.PythonExecutablePath.Replace(DataLocation.RoamingDataPath, DataLocation.PortableDataPath); + } + + if (IsUsingRoamingPath(settings.PluginSettings.NodeExecutablePath)) + { + settings.PluginSettings.NodeExecutablePath + = settings.PluginSettings.NodeExecutablePath.Replace(DataLocation.RoamingDataPath, DataLocation.PortableDataPath); + } + } + 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; + + // 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}"; + + return filePath.Contains(portableAppEnvLocation); + } + + private static bool IsUsingRoamingPath(string filePath) + { + if (string.IsNullOrEmpty(filePath)) + return false; + + return filePath.StartsWith(DataLocation.RoamingDataPath); + } + + private static string GetUpdatedEnvironmentPath(string filePath) + { + 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}"; + } + } +} diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs new file mode 100644 index 000000000..b67059b1b --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/JavaScriptEnvironment.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Core.ExternalPlugins.Environments +{ + + internal class JavaScriptEnvironment : TypeScriptEnvironment + { + internal override string Language => AllowedLanguage.JavaScript; + + internal JavaScriptEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } + } +} diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs new file mode 100644 index 000000000..55b3b60bd --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/PythonEnvironment.cs @@ -0,0 +1,48 @@ +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 +{ + internal class PythonEnvironment : AbstractPluginEnvironment + { + internal override string Language => AllowedLanguage.Python; + + internal override string EnvName => DataLocation.PythonEnvironmentName; + + internal override string EnvPath => Path.Combine(DataLocation.PluginEnvironmentsPath, EnvName); + + internal override string InstallPath => Path.Combine(EnvPath, "PythonEmbeddable-v3.8.9"); + + internal override string ExecutablePath => Path.Combine(InstallPath, "pythonw.exe"); + + internal override string FileDialogFilter => "Python|pythonw.exe"; + + internal override string PluginsSettingsFilePath { get => PluginSettings.PythonExecutablePath; set => PluginSettings.PythonExecutablePath = value; } + + internal PythonEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } + + internal override void InstallEnvironment() + { + FilesFolders.RemoveFolderIfExists(InstallPath); + + // Python 3.8.9 is used for Windows 7 compatibility + DroplexPackage.Drop(App.python_3_8_9_embeddable, InstallPath).Wait(); + + PluginsSettingsFilePath = ExecutablePath; + } + + internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata) + { + return new PluginPair + { + Plugin = new PythonPlugin(filePath), + Metadata = metadata + }; + } + } +} diff --git a/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs new file mode 100644 index 000000000..70341f711 --- /dev/null +++ b/Flow.Launcher.Core/ExternalPlugins/Environments/TypeScriptEnvironment.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Droplex; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin.SharedCommands; +using Flow.Launcher.Plugin; +using System.IO; +using Flow.Launcher.Core.Plugin; + +namespace Flow.Launcher.Core.ExternalPlugins.Environments +{ + internal class TypeScriptEnvironment : AbstractPluginEnvironment + { + internal override string Language => AllowedLanguage.TypeScript; + + internal override string EnvName => DataLocation.NodeEnvironmentName; + + internal override string EnvPath => Path.Combine(DataLocation.PluginEnvironmentsPath, EnvName); + + 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 TypeScriptEnvironment(List pluginMetadataList, PluginsSettings pluginSettings) : base(pluginMetadataList, pluginSettings) { } + + internal override void InstallEnvironment() + { + FilesFolders.RemoveFolderIfExists(InstallPath); + + DroplexPackage.Drop(App.nodejs_16_18_0, InstallPath).Wait(); + + PluginsSettingsFilePath = ExecutablePath; + } + + internal override PluginPair CreatePluginPair(string filePath, PluginMetadata metadata) + { + return new PluginPair + { + Plugin = new NodePlugin(filePath), + Metadata = metadata + }; + } + } +} diff --git a/Flow.Launcher.Core/Flow.Launcher.Core.csproj b/Flow.Launcher.Core/Flow.Launcher.Core.csproj index ec99dc520..beb2925bf 100644 --- a/Flow.Launcher.Core/Flow.Launcher.Core.csproj +++ b/Flow.Launcher.Core/Flow.Launcher.Core.csproj @@ -53,7 +53,7 @@ - + diff --git a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs index 049d1c583..6b55bb3e3 100644 --- a/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs +++ b/Flow.Launcher.Core/Plugin/ExecutablePlugin.cs @@ -9,7 +9,6 @@ namespace Flow.Launcher.Core.Plugin internal class ExecutablePlugin : JsonRPCPlugin { private readonly ProcessStartInfo _startInfo; - public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable; public ExecutablePlugin(string filename) { diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 4df5037a5..28d57501b 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -37,10 +37,7 @@ namespace Flow.Launcher.Core.Plugin { protected PluginInitContext context; public const string JsonRPC = "JsonRPC"; - /// - /// The language this JsonRPCPlugin support - /// - public abstract string SupportedLanguage { get; set; } + protected abstract Task RequestAsync(JsonRPCRequestModel rpcRequest, CancellationToken token = default); protected abstract string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default); diff --git a/Flow.Launcher.Core/Plugin/NodePlugin.cs b/Flow.Launcher.Core/Plugin/NodePlugin.cs new file mode 100644 index 000000000..1247143fa --- /dev/null +++ b/Flow.Launcher.Core/Plugin/NodePlugin.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Flow.Launcher.Plugin; + +namespace Flow.Launcher.Core.Plugin +{ + /// + /// Execution of JavaScript & TypeScript plugins + /// + internal class NodePlugin : JsonRPCPlugin + { + private readonly ProcessStartInfo _startInfo; + + public NodePlugin(string filename) + { + _startInfo = new ProcessStartInfo + { + FileName = filename, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + } + + protected override Task RequestAsync(JsonRPCRequestModel request, CancellationToken token = default) + { + _startInfo.ArgumentList[1] = request.ToString(); + return ExecuteAsync(_startInfo, token); + } + + protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default) + { + // since this is not static, request strings will build up in ArgumentList if index is not specified + _startInfo.ArgumentList[1] = rpcRequest.ToString(); + return Execute(_startInfo); + } + + public override async Task InitAsync(PluginInitContext context) + { + _startInfo.ArgumentList.Add(context.CurrentPluginMetadata.ExecuteFilePath); + _startInfo.ArgumentList.Add(string.Empty); + await base.InitAsync(context); + _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; + } + } +} diff --git a/Flow.Launcher.Core/Plugin/PluginsLoader.cs b/Flow.Launcher.Core/Plugin/PluginsLoader.cs index 752174263..e6329aba1 100644 --- a/Flow.Launcher.Core/Plugin/PluginsLoader.cs +++ b/Flow.Launcher.Core/Plugin/PluginsLoader.cs @@ -1,30 +1,38 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; -using Droplex; -using Flow.Launcher.Infrastructure; +using Flow.Launcher.Core.ExternalPlugins.Environments; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using Flow.Launcher.Plugin.SharedCommands; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Core.Plugin { public static class PluginsLoader { - public const string PythonExecutable = "pythonw.exe"; - public static List Plugins(List metadatas, PluginsSettings settings) { var dotnetPlugins = DotNetPlugins(metadatas); - var pythonPlugins = PythonPlugins(metadatas, settings); + + var pythonEnv = new PythonEnvironment(metadatas, settings); + var tsEnv = new TypeScriptEnvironment(metadatas, settings); + var jsEnv = new JavaScriptEnvironment(metadatas, settings); + var pythonPlugins = pythonEnv.Setup(); + var tsPlugins = tsEnv.Setup(); + var jsPlugins = jsEnv.Setup(); + var executablePlugins = ExecutablePlugins(metadatas); - var plugins = dotnetPlugins.Concat(pythonPlugins).Concat(executablePlugins).ToList(); + + var plugins = dotnetPlugins + .Concat(pythonPlugins) + .Concat(tsPlugins) + .Concat(jsPlugins) + .Concat(executablePlugins) + .ToList(); return plugins; } @@ -108,97 +116,10 @@ namespace Flow.Launcher.Core.Plugin return plugins; } - public static IEnumerable PythonPlugins(List source, PluginsSettings settings) - { - if (!source.Any(o => o.Language.ToUpper() == AllowedLanguage.Python)) - return new List(); - - if (!string.IsNullOrEmpty(settings.PythonDirectory) && FilesFolders.LocationExists(settings.PythonDirectory)) - return SetPythonPathForPluginPairs(source, Path.Combine(settings.PythonDirectory, PythonExecutable)); - - var pythonPath = string.Empty; - - if (MessageBox.Show("Flow detected you have installed Python plugins, which " + - "will need Python to run. Would you like to download Python? " + - Environment.NewLine + Environment.NewLine + - "Click no if it's already installed, " + - "and you will be prompted to select the folder that contains the Python executable", - string.Empty, MessageBoxButtons.YesNo) == DialogResult.No - && string.IsNullOrEmpty(settings.PythonDirectory)) - { - var dlg = new FolderBrowserDialog - { - SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles) - }; - - var result = dlg.ShowDialog(); - if (result == DialogResult.OK) - { - string pythonDirectory = dlg.SelectedPath; - if (!string.IsNullOrEmpty(pythonDirectory)) - { - pythonPath = Path.Combine(pythonDirectory, PythonExecutable); - if (File.Exists(pythonPath)) - { - settings.PythonDirectory = pythonDirectory; - Constant.PythonPath = pythonPath; - } - else - { - MessageBox.Show("Can't find python in given directory"); - } - } - } - } - else - { - var installedPythonDirectory = Path.Combine(DataLocation.DataDirectory(), "PythonEmbeddable"); - - // Python 3.8.9 is used for Windows 7 compatibility - DroplexPackage.Drop(App.python_3_8_9_embeddable, installedPythonDirectory).Wait(); - - pythonPath = Path.Combine(installedPythonDirectory, PythonExecutable); - if (FilesFolders.FileExists(pythonPath)) - { - settings.PythonDirectory = installedPythonDirectory; - Constant.PythonPath = pythonPath; - } - else - { - Log.Error("PluginsLoader", - $"Failed to set Python path after Droplex install, {pythonPath} does not exist", - "PythonPlugins"); - } - } - - if (string.IsNullOrEmpty(settings.PythonDirectory) || string.IsNullOrEmpty(pythonPath)) - { - MessageBox.Show( - "Unable to set Python executable path, please try from Flow's settings (scroll down to the bottom)."); - Log.Error("PluginsLoader", - $"Not able to successfully set Python path, the PythonDirectory variable is still an empty string.", - "PythonPlugins"); - - return new List(); - } - - return SetPythonPathForPluginPairs(source, pythonPath); - } - - private static IEnumerable SetPythonPathForPluginPairs(List source, string pythonPath) - => source - .Where(o => o.Language.ToUpper() == AllowedLanguage.Python) - .Select(metadata => new PluginPair - { - Plugin = new PythonPlugin(pythonPath), - Metadata = metadata - }) - .ToList(); - public static IEnumerable ExecutablePlugins(IEnumerable source) { return source - .Where(o => o.Language.ToUpper() == AllowedLanguage.Executable) + .Where(o => o.Language.Equals(AllowedLanguage.Executable, StringComparison.OrdinalIgnoreCase)) .Select(metadata => new PluginPair { Plugin = new ExecutablePlugin(metadata.ExecuteFilePath), Metadata = metadata diff --git a/Flow.Launcher.Core/Plugin/PythonPlugin.cs b/Flow.Launcher.Core/Plugin/PythonPlugin.cs index 8f7e5760a..2bbf6d110 100644 --- a/Flow.Launcher.Core/Plugin/PythonPlugin.cs +++ b/Flow.Launcher.Core/Plugin/PythonPlugin.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using System.IO; using System.Threading; @@ -11,7 +11,6 @@ namespace Flow.Launcher.Core.Plugin internal class PythonPlugin : JsonRPCPlugin { private readonly ProcessStartInfo _startInfo; - public override string SupportedLanguage { get; set; } = AllowedLanguage.Python; public PythonPlugin(string filename) { @@ -46,6 +45,7 @@ namespace Flow.Launcher.Core.Plugin protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default) { + // since this is not static, request strings will build up in ArgumentList if index is not specified _startInfo.ArgumentList[2] = rpcRequest.ToString(); _startInfo.WorkingDirectory = context.CurrentPluginMetadata.PluginDirectory; // TODO: Async Action diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 44c47cf28..a0baa51ba 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -144,6 +144,5 @@ namespace Flow.Launcher.Core return tips; } - } } diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index 56f421e30..ab5e4722b 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -32,6 +32,7 @@ namespace Flow.Launcher.Infrastructure public static readonly string LoadingImgIcon = Path.Combine(ImagesDirectory, "loading.png"); public static string PythonPath; + public static string NodePath; public static readonly string QueryTextBoxIconImagePath = $"{ProgramDirectory}\\Images\\mainsearch.svg"; diff --git a/Flow.Launcher.Infrastructure/Exception/ExceptionFormatter.cs b/Flow.Launcher.Infrastructure/Exception/ExceptionFormatter.cs index 40ac6b121..54c19c048 100644 --- a/Flow.Launcher.Infrastructure/Exception/ExceptionFormatter.cs +++ b/Flow.Launcher.Infrastructure/Exception/ExceptionFormatter.cs @@ -67,6 +67,7 @@ namespace Flow.Launcher.Infrastructure.Exception sb.AppendLine($"* IntPtr Length: {IntPtr.Size}"); sb.AppendLine($"* x64: {Environment.Is64BitOperatingSystem}"); sb.AppendLine($"* Python Path: {Constant.PythonPath}"); + sb.AppendLine($"* Node Path: {Constant.NodePath}"); sb.AppendLine($"* CLR Version: {Environment.Version}"); sb.AppendLine($"* Installed .NET Framework: "); foreach (var result in GetFrameworkVersionFromRegistry()) diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 0450d91d2..e7b1f46f7 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -86,6 +86,21 @@ namespace Flow.Launcher.Infrastructure.Image return key is not null && Data.ContainsKey((key, isFullImage)) && Data[(key, isFullImage)].imageSource != null; } + public bool TryGetValue(string key, bool isFullImage, out ImageSource image) + { + if (key is not null) + { + bool hasKey = Data.TryGetValue((key, isFullImage), out var imageUsage); + image = hasKey ? imageUsage.imageSource : null; + return hasKey; + } + else + { + image = null; + return false; + } + } + public int CacheSize() { return Data.Count; diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index ba8292b26..62524f03a 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -251,6 +251,11 @@ namespace Flow.Launcher.Infrastructure.Image return ImageCache.ContainsKey(path, loadFullImage); } + public static bool TryGetValue(string path, bool loadFullImage, out ImageSource image) + { + return ImageCache.TryGetValue(path, loadFullImage, out image); + } + public static async ValueTask LoadAsync(string path, bool loadFullImage = false) { var imageResult = await LoadInternalAsync(path, loadFullImage); diff --git a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs index e93c341dc..e294f52b8 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/DataLocation.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; +using System; using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Flow.Launcher.Infrastructure.UserSettings { @@ -31,5 +27,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings public static readonly string PluginsDirectory = Path.Combine(DataDirectory(), Constant.Plugins); public static readonly string PluginSettingsDirectory = Path.Combine(DataDirectory(), "Settings", Constant.Plugins); + + public const string PythonEnvironmentName = "Python"; + public const string NodeEnvironmentName = "Node.js"; + public const string PluginEnvironments = "Environments"; + public static readonly string PluginEnvironmentsPath = Path.Combine(DataDirectory(), PluginEnvironments); } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs index c06e1587d..130e25d7b 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/PluginSettings.cs @@ -1,11 +1,34 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Flow.Launcher.Plugin; namespace Flow.Launcher.Infrastructure.UserSettings { public class PluginsSettings : BaseModel { + private string pythonExecutablePath = string.Empty; + public string PythonExecutablePath { + get { return pythonExecutablePath; } + set + { + pythonExecutablePath = value; + Constant.PythonPath = value; + } + } + + private string nodeExecutablePath = string.Empty; + public string NodeExecutablePath + { + get { return nodeExecutablePath; } + set + { + nodeExecutablePath = value; + Constant.NodePath = value; + } + } + + // TODO: Remove. This is backwards compatibility for 1.10.0 release. public string PythonDirectory { get; set; } + public Dictionary Plugins { get; set; } = new Dictionary(); public void UpdatePluginSettings(List metadatas) diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 9ae1748da..a184520d0 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -18,6 +18,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings public string ColorScheme { get; set; } = "System"; public bool ShowOpenResultHotkey { get; set; } = true; public double WindowSize { get; set; } = 580; + public string PreviewHotkey { get; set; } = $"F1"; public string Language { diff --git a/Flow.Launcher.Plugin/AllowedLanguage.cs b/Flow.Launcher.Plugin/AllowedLanguage.cs index 94c645d27..d5eea4fa5 100644 --- a/Flow.Launcher.Plugin/AllowedLanguage.cs +++ b/Flow.Launcher.Plugin/AllowedLanguage.cs @@ -1,4 +1,6 @@ -namespace Flow.Launcher.Plugin +using System; + +namespace Flow.Launcher.Plugin { /// /// Allowed plugin languages @@ -8,34 +10,32 @@ /// /// Python /// - public static string Python - { - get { return "PYTHON"; } - } + public const string Python = "Python"; /// /// C# /// - public static string CSharp - { - get { return "CSHARP"; } - } + public const string CSharp = "CSharp"; /// /// F# /// - public static string FSharp - { - get { return "FSHARP"; } - } + public const string FSharp = "FSharp"; /// /// Standard .exe /// - public static string Executable - { - get { return "EXECUTABLE"; } - } + public const string Executable = "Executable"; + + /// + /// TypeScript + /// + public const string TypeScript = "TypeScript"; + + /// + /// JavaScript + /// + public const string JavaScript = "JavaScript"; /// /// Determines if this language is a .NET language @@ -44,8 +44,8 @@ /// public static bool IsDotNet(string language) { - return language.ToUpper() == CSharp - || language.ToUpper() == FSharp; + return language.Equals(CSharp, StringComparison.OrdinalIgnoreCase) + || language.Equals(FSharp, StringComparison.OrdinalIgnoreCase); } /// @@ -56,8 +56,10 @@ public static bool IsAllowed(string language) { return IsDotNet(language) - || language.ToUpper() == Python.ToUpper() - || language.ToUpper() == Executable.ToUpper(); + || language.Equals(Python, StringComparison.OrdinalIgnoreCase) + || language.Equals(Executable, StringComparison.OrdinalIgnoreCase) + || language.Equals(TypeScript, StringComparison.OrdinalIgnoreCase) + || language.Equals(JavaScript, StringComparison.OrdinalIgnoreCase); } } } diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index 912a23a6f..dadf220e3 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -246,12 +246,14 @@ namespace Flow.Launcher.Plugin /// public bool IsMedia { get; set; } public string Description { get; set; } + public IconDelegate PreviewDelegate { get; set; } public static PreviewInfo Default { get; } = new() { PreviewImagePath = null, Description = null, IsMedia = false, + PreviewDelegate = null, }; } } diff --git a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs index fb91c6388..765280e08 100644 --- a/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs +++ b/Flow.Launcher.Test/Plugins/JsonRPCPluginTest.cs @@ -1,4 +1,4 @@ -using NUnit; +using NUnit; using NUnit.Framework; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Plugin; @@ -16,8 +16,6 @@ namespace Flow.Launcher.Test.Plugins // ReSharper disable once InconsistentNaming internal class JsonRPCPluginTest : JsonRPCPlugin { - public override string SupportedLanguage { get; set; } = AllowedLanguage.Executable; - protected override string Request(JsonRPCRequestModel rpcRequest, CancellationToken token = default) { throw new System.NotImplementedException(); diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 350371461..43fa0eddb 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -6,6 +6,7 @@ using System.Timers; using System.Windows; using Flow.Launcher.Core; using Flow.Launcher.Core.Configuration; +using Flow.Launcher.Core.ExternalPlugins.Environments; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; @@ -61,6 +62,8 @@ namespace Flow.Launcher _settingsVM = new SettingWindowViewModel(_updater, _portable); _settings = _settingsVM.Settings; + AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings); + _alphabet.Initialize(_settings); _stringMatcher = new StringMatcher(_alphabet); StringMatcher.Instance = _stringMatcher; diff --git a/Flow.Launcher/Converters/StringToKeyBindingConverter.cs b/Flow.Launcher/Converters/StringToKeyBindingConverter.cs new file mode 100644 index 000000000..3675f06fc --- /dev/null +++ b/Flow.Launcher/Converters/StringToKeyBindingConverter.cs @@ -0,0 +1,32 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Input; + +namespace Flow.Launcher.Converters +{ + class StringToKeyBindingConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + var mode = parameter as string; + var hotkeyStr = value as string; + var converter = new KeyGestureConverter(); + var key = (KeyGesture)converter.ConvertFromString(hotkeyStr); + if (mode == "key") + { + return key.Key; + } + else if (mode == "modifiers") + { + return key.Modifiers; + } + return null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Flow.Launcher/Helper/ErrorReporting.cs b/Flow.Launcher/Helper/ErrorReporting.cs index f3f590167..a7ce7444c 100644 --- a/Flow.Launcher/Helper/ErrorReporting.cs +++ b/Flow.Launcher/Helper/ErrorReporting.cs @@ -43,8 +43,8 @@ namespace Flow.Launcher.Helper public static string DependenciesInfo() { - var info = $"\nPython Path: {Constant.PythonPath}"; + var info = $"\nPython Path: {Constant.PythonPath}\nNode Path: {Constant.NodePath}"; return info; } } -} \ No newline at end of file +} diff --git a/Flow.Launcher/Languages/da.xaml b/Flow.Launcher/Languages/da.xaml index e6eec4560..0778d4f97 100644 --- a/Flow.Launcher/Languages/da.xaml +++ b/Flow.Launcher/Languages/da.xaml @@ -53,9 +53,8 @@ Select the file manager to use when opening the folder. Default Web Browser Setting for New Tab, New Window, Private Mode. - Python bibliotek Autoopdatering - Vælg + Vælg Skjul Flow Launcher ved opstart Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/Languages/de.xaml b/Flow.Launcher/Languages/de.xaml index 2073fd424..025eba3b4 100644 --- a/Flow.Launcher/Languages/de.xaml +++ b/Flow.Launcher/Languages/de.xaml @@ -53,9 +53,8 @@ Wählen Sie den Dateimanager, der beim Öffnen des Ordners verwendet werden soll. Standardbrowser Einstellung für neuen Tab, neues Fenster und dem Privatmodus. - Python-Verzeichnis Automatische Aktualisierung - Auswählen + Auswählen Verstecke Flow Launcher bei Systemstart Statusleistensymbol ausblenden When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Pinyin aktivieren Ermöglicht die Verwendung von Pinyin für die Suche. Pinyin ist das Standardsystem der romanisierten Schreibweise für die Übersetzung von chinesischen Texten. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Der Schatteneffekt ist nicht zulässig, wenn das aktuelle Thema den Weichzeichneffekt aktiviert hat diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 550b73f85..430bdef26 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -57,11 +57,14 @@ Select the file manager to use when opening the folder. Default Web Browser Setting for New Tab, New Window, Private Mode. + Python Path + Node.js Path + Please select the Node.js executable + Please select pythonw.exe Always Start Typing in English Mode Temporarily change your input method to English mode when activating Flow. - Python Directory Auto Update - Select + Select Hide Flow Launcher on startup Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -70,7 +73,7 @@ Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled @@ -153,6 +156,8 @@ Hotkey Flow Launcher Hotkey Enter shortcut to show/hide Flow Launcher. + Preview Hotkey + Enter shortcut to show/hide preview in search window. Open Result Modifier Key Select a modifier key to open selected result via keyboard. Show Hotkey @@ -293,7 +298,7 @@ Please wait... - + Checking for new update You already have the latest Flow Launcher version Update found diff --git a/Flow.Launcher/Languages/es-419.xaml b/Flow.Launcher/Languages/es-419.xaml index 3a4317ca9..212e8c1fe 100644 --- a/Flow.Launcher/Languages/es-419.xaml +++ b/Flow.Launcher/Languages/es-419.xaml @@ -53,9 +53,8 @@ Seleccione el gestor de archivos a utilizar al abrir la carpeta. Navegador web predeterminado Configuración para Nueva Pestaña, Nueva Ventana, Modo Privado. - Directorio de Python Actualización automática - Seleccionar + Seleccionar Ocultar Flow Launcher al arrancar el sistema Ocultar icono de la bandeja When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. El efecto de sombra no está permitido mientras el tema actual tenga el efecto de desenfoque habilitado diff --git a/Flow.Launcher/Languages/es.xaml b/Flow.Launcher/Languages/es.xaml index b3dbaf4ff..f2f5b7709 100644 --- a/Flow.Launcher/Languages/es.xaml +++ b/Flow.Launcher/Languages/es.xaml @@ -53,9 +53,8 @@ Selecciona el administrador de archivos que se desea utilizar para abrir la carpeta. Navegador web predeterminado Configuración para Nueva Pestaña, Nueva Ventana, Modo Privado. - Carpeta de Python Actualización automática - Seleccionar + Seleccionar Ocultar Flow Launcher al inicio Ocultar icono de la bandeja del sistema Cuando el icono está oculto en la bandeja del sistema, se puede abrir el menú de configuración haciendo clic con el botón derecho en la ventana de búsqueda. @@ -64,7 +63,7 @@ Buscar con Pinyin Permite utilizar Pinyin para la búsqueda. Pinyin es el sistema estándar de ortografía romanizado para traducir chino. Mostrar siempre vista previa - Muestra siempre el panel de vista previa al iniciar Flow. Pulse F1 para mostrar/ocultar la vista previa. + Muestra siempre el panel de vista previa al iniciar Flow. Pulse {0} para mostrar/ocultar la vista previa. El efecto de sombra no está permitido mientras el tema actual tenga el efecto de desenfoque activado diff --git a/Flow.Launcher/Languages/fr.xaml b/Flow.Launcher/Languages/fr.xaml index 4664b871d..4820757ba 100644 --- a/Flow.Launcher/Languages/fr.xaml +++ b/Flow.Launcher/Languages/fr.xaml @@ -53,9 +53,8 @@ Select the file manager to use when opening the folder. Navigateur web par défaut Setting for New Tab, New Window, Private Mode. - Répertoire de Python Mettre à jour automatiquement - Sélectionner + Sélectionner Cacher Flow Launcher au démarrage Masquer icône du plateau When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Devrait utiliser le pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/Languages/it.xaml b/Flow.Launcher/Languages/it.xaml index 84fcad8c8..2b1b16cab 100644 --- a/Flow.Launcher/Languages/it.xaml +++ b/Flow.Launcher/Languages/it.xaml @@ -53,9 +53,8 @@ Selezionare il Gestore file da usare all'apertura della cartella. Browser predefinito Impostazione per Nuova scheda, Nuova finestra, Modalità privata. - Cartella Python Aggiornamento automatico - Seleziona + Seleziona Nascondi Flow Launcher all'avvio Nascondi Icona nell'Area di Notifica When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Dovrebbe usare il Pinyin Consente di utilizzare il Pinyin per la ricerca. Il Pinyin è il sistema standard di ortografia romanizzata per la traduzione del cinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. L'effetto ombra non è consentito mentre il tema corrente ha un effetto di sfocatura abilitato diff --git a/Flow.Launcher/Languages/ja.xaml b/Flow.Launcher/Languages/ja.xaml index 816602f1c..4a743abd5 100644 --- a/Flow.Launcher/Languages/ja.xaml +++ b/Flow.Launcher/Languages/ja.xaml @@ -53,9 +53,8 @@ Select the file manager to use when opening the folder. Default Web Browser Setting for New Tab, New Window, Private Mode. - Pythonのディレクトリ 自動更新 - 選択 + 選択 起動時にFlow Launcherを隠す トレイアイコンを隠す When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml index f89a28454..9efebbaf6 100644 --- a/Flow.Launcher/Languages/ko.xaml +++ b/Flow.Launcher/Languages/ko.xaml @@ -53,9 +53,8 @@ 폴더를 열 때 사용할 파일관리자를 선택하세요. 기본 웹 브라우저 새 탭, 새 창, 사생활 보호 모드 - Python 디렉토리 자동 업데이트 - 선택 + 선택 시작 시 Flow Launcher 숨김 트레이 아이콘 숨기기 트레이에서 아이콘을 숨길 경우, 검색창 우클릭으로 설정창을 열 수 있습니다. @@ -64,7 +63,7 @@ 항상 Pinyin 사용 Pinyin을 사용하여 검색할 수 있습니다. Pinyin (병음) 은 로마자 중국어 입력 방식입니다. 항상 미리보기 - 항상 미리보기 패널이 열린 상태로 Flow를 시작합니다. F1키로 미리보기를 on/off 합니다. + 항상 미리보기 패널이 열린 상태로 Flow를 시작합니다. {0} 키로 미리보기를 on/off 합니다. 반투명 흐림 효과를 사용하는 경우, 그림자 효과를 쓸 수 없습니다. diff --git a/Flow.Launcher/Languages/nb-NO.xaml b/Flow.Launcher/Languages/nb-NO.xaml index 859ec20a0..c8b1e4456 100644 --- a/Flow.Launcher/Languages/nb-NO.xaml +++ b/Flow.Launcher/Languages/nb-NO.xaml @@ -28,9 +28,8 @@ Tøm siste spørring Maks antall resultater vist Ignorer hurtigtaster i fullskjermsmodus - Python-mappe Oppdater automatisk - Velg + Velg Skjul Flow Launcher ved oppstart diff --git a/Flow.Launcher/Languages/nb.xaml b/Flow.Launcher/Languages/nb.xaml index 5356890e3..11d88a32b 100644 --- a/Flow.Launcher/Languages/nb.xaml +++ b/Flow.Launcher/Languages/nb.xaml @@ -53,9 +53,8 @@ Select the file manager to use when opening the folder. Default Web Browser Setting for New Tab, New Window, Private Mode. - Python Directory Auto Update - Select + Select Hide Flow Launcher on startup Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/Languages/nl.xaml b/Flow.Launcher/Languages/nl.xaml index a7af3cc57..322a96be4 100644 --- a/Flow.Launcher/Languages/nl.xaml +++ b/Flow.Launcher/Languages/nl.xaml @@ -53,9 +53,8 @@ Selecteer de bestandsbeheerder voor het openen van de map. Standaard webbrowser Instelling voor Nieuw tabblad, Nieuw Venster, Privémodus. - Python map Automatische Update - Selecteer + Selecteer Verberg Flow Launcher als systeem opstart Systeemvakpictogram verbergen When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Zou Pinyin moeten gebruiken Zorgt ervoor dat Pinyin gebruikt kan worden om te zoeken. Pinyin is het standaard systeem van geromaniseerde spelling voor het vertalen van Chinees. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Schaduw effect is niet toegestaan omdat het huidige thema een vervagingseffect heeft diff --git a/Flow.Launcher/Languages/pl.xaml b/Flow.Launcher/Languages/pl.xaml index 0a7ccf070..9df50d6db 100644 --- a/Flow.Launcher/Languages/pl.xaml +++ b/Flow.Launcher/Languages/pl.xaml @@ -53,9 +53,8 @@ Wybierz menedżer plików używany do otwierania folderów. Domyślna przeglądarka Ustawienie dla nowej karty, nowego okna i trybu prywatnego. - Folder biblioteki Python Automatyczne aktualizacje - Wybierz + Wybierz Uruchamiaj Flow Launcher zminimalizowany Ukryj ikonę zasobnika When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/Languages/pt-br.xaml b/Flow.Launcher/Languages/pt-br.xaml index c308d3367..8649be772 100644 --- a/Flow.Launcher/Languages/pt-br.xaml +++ b/Flow.Launcher/Languages/pt-br.xaml @@ -53,9 +53,8 @@ Select the file manager to use when opening the folder. Default Web Browser Setting for New Tab, New Window, Private Mode. - Diretório Python Atualizar Automaticamente - Selecionar + Selecionar Esconder Flow Launcher na inicialização Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/Languages/pt-pt.xaml b/Flow.Launcher/Languages/pt-pt.xaml index a06d0e935..f9e8357af 100644 --- a/Flow.Launcher/Languages/pt-pt.xaml +++ b/Flow.Launcher/Languages/pt-pt.xaml @@ -53,9 +53,8 @@ Selecione o gestor de ficheiros utilizado para abrir a página Navegador web padrão Definições para Novo separador, Nova Janela e Modo privado - Diretório Python Atualização automática - Selecionar + Selecionar Ocultar Flow Launcher ao arrancar Ocultar ícone na bandeja Se o ícone da bandeja estiver oculto, pode abrir as Definições com um clique com o botão direito do rato na caixa de pesquisa. @@ -64,7 +63,7 @@ Pesquisar com Pinyin Permite a utilização de Pinyin para pesquisar. Pinyin é um sistema normalizado de ortografia romanizada para tradução de mandarim. Pré-visualizar sempre - Abrir painel de pré-visualização ao iniciar a aplicação. Prima F1 para comutar esta opção. + Abrir painel de pré-visualização ao iniciar a aplicação. Prima {0} para comutar esta opção. O efeito sombra não é permitido com este tema porque o efeito desfocar está ativo diff --git a/Flow.Launcher/Languages/ru.xaml b/Flow.Launcher/Languages/ru.xaml index 471f663eb..be104b988 100644 --- a/Flow.Launcher/Languages/ru.xaml +++ b/Flow.Launcher/Languages/ru.xaml @@ -53,9 +53,8 @@ Select the file manager to use when opening the folder. Default Web Browser Setting for New Tab, New Window, Private Mode. - Python Directory Auto Update - Select + Select Hide Flow Launcher on startup Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/Languages/sk.xaml b/Flow.Launcher/Languages/sk.xaml index 97223e46c..3f08eac90 100644 --- a/Flow.Launcher/Languages/sk.xaml +++ b/Flow.Launcher/Languages/sk.xaml @@ -53,9 +53,8 @@ Vyberte správcu súborov, ktorý sa má použiť pri otváraní priečinka. Predvolený webový prehliadač Nastavenie pre novú kartu, nové okno, privátny režim. - Priečinok s Pythonom Automatická aktualizácia - Vybrať + Vybrať Schovať Flow Launcher po spustení Schovať ikonu z oblasti oznámení Keď je ikona skrytá z oblasti oznámení, nastavenia možno otvoriť kliknutím pravým tlačidlom myši na okno vyhľadávania. @@ -64,7 +63,7 @@ Vyhľadávanie pomocou pchin-jin Umožňuje vyhľadávanie pomocou pchin-jin. Pchin-jin je systém zápisu čínskeho jazyka pomocou písmen latinky. Vždy zobraziť náhľad - Pri spustení Flowu vždy otvoriť panel s náhľadom. Stlačením klávesu F1 prepnete náhľad. + Pri spustení Flowu vždy otvoriť panel s náhľadom. Stlačením klávesu {0} prepnete náhľad. Efekt tieňa nie je povolený, kým má aktuálny motív povolený efekt rozostrenia diff --git a/Flow.Launcher/Languages/sr.xaml b/Flow.Launcher/Languages/sr.xaml index 0ffb86fac..7f710286e 100644 --- a/Flow.Launcher/Languages/sr.xaml +++ b/Flow.Launcher/Languages/sr.xaml @@ -53,9 +53,8 @@ Select the file manager to use when opening the folder. Default Web Browser Setting for New Tab, New Window, Private Mode. - Python direktorijum Auto ažuriranje - Izaberi + Izaberi Sakrij Flow Launcher pri podizanju sistema Hide tray icon When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Search with Pinyin Allows using Pinyin to search. Pinyin is the standard system of romanized spelling for translating Chinese. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/Languages/tr.xaml b/Flow.Launcher/Languages/tr.xaml index c9c11dbd5..c9b2bf70e 100644 --- a/Flow.Launcher/Languages/tr.xaml +++ b/Flow.Launcher/Languages/tr.xaml @@ -53,9 +53,8 @@ Klasör açarken kullanılacak dosya yöneticisini seçin. Varsayılan Tarayıcısı Yeni Sekme, Yeni Pencere, Gizli Mod için Ayar. - Python Konumu Otomatik Güncelle - Seç + Seç Başlangıçta Flow Launcher'u gizle Sistem çekmecesi simgesini gizle When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Pinyin kullanılmalı Arama yapmak için Pinyin'in kullanılmasına izin verir. Pinyin, Çince'yi çevirmek için standart romanlaştırılmış yazım sistemidir. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Mevcut temada bulanıklık efekti etkinken gölge efektine izin verilmez diff --git a/Flow.Launcher/Languages/uk-UA.xaml b/Flow.Launcher/Languages/uk-UA.xaml index e8b2048d8..88c251429 100644 --- a/Flow.Launcher/Languages/uk-UA.xaml +++ b/Flow.Launcher/Languages/uk-UA.xaml @@ -53,9 +53,8 @@ Виберіть файловий менеджер для використання під час відкриття теки. Браузер за замовчуванням Налаштування нової вкладки, нового вікна, приватного режиму. - Директорія Python Автоматичне оновлення - Вибрати + Вибрати Сховати Flow Launcher при запуску системи Приховати значок в системному лотку When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ Використовувати піньїнь Дозволяє використовувати пінїнь для пошуку. Піньїнь - це стандартна система написання для перекладу китайської. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Ефект тіні не дозволено, коли поточна тема має ефект розмиття diff --git a/Flow.Launcher/Languages/zh-cn.xaml b/Flow.Launcher/Languages/zh-cn.xaml index e4518d6c3..acb17b8b9 100644 --- a/Flow.Launcher/Languages/zh-cn.xaml +++ b/Flow.Launcher/Languages/zh-cn.xaml @@ -53,9 +53,8 @@ 选择打开文件夹时要使用的文件管理器。 默认浏览器 新标签/窗口及隐身模式设置。 - Python 路径 自动更新 - 选择 + 选择 系统启动时不显示主窗口 隐藏任务栏图标 任务栏图标被隐藏时,右键点击搜索窗口即可打开设置菜单。 @@ -64,7 +63,7 @@ 使用 Pinyin 搜索 允许使用拼音进行搜索. 始终打开预览 - Flow 启动时总是打开预览面板。按 F1 以切换预览。 + Flow 启动时总是打开预览面板。按 {0} 以切换预览。 当前主题已启用模糊效果,不允许启用阴影效果 diff --git a/Flow.Launcher/Languages/zh-tw.xaml b/Flow.Launcher/Languages/zh-tw.xaml index e5a8800f7..8fa1b001a 100644 --- a/Flow.Launcher/Languages/zh-tw.xaml +++ b/Flow.Launcher/Languages/zh-tw.xaml @@ -53,9 +53,8 @@ 選擇開啟資料夾時要使用的檔案管理器。 預設瀏覽器 設定新增分頁、視窗和無痕模式。 - Python 路徑 自動更新 - 選擇 + 選擇 啟動時不顯示主視窗 隱藏任務欄圖標 When the icon is hidden from the tray, the Settings menu can be opened by right-clicking on the search window. @@ -64,7 +63,7 @@ 拼音搜索 允許使用拼音來搜索. Always Preview - Always open preview panel when Flow starts. Press F1 to toggle preview. + Always open preview panel when Flow activates. Press {0} to toggle preview. Shadow effect is not allowed while current theme has blur effect enabled diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 5ed9ba802..6ef8353d3 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -40,10 +40,10 @@ + - + @@ -343,7 +347,7 @@ + Grid.ColumnSpan="{Binding ResultAreaColumn}"> @@ -397,11 +401,13 @@ x:Name="Preview" Grid.Column="1" VerticalAlignment="Stretch" - d:DataContext="{d:DesignInstance vm:ResultViewModel}" - DataContext="{Binding SelectedItem, ElementName=ResultListBox}" Style="{DynamicResource PreviewArea}" - Visibility="Collapsed"> - + Visibility="{Binding PreviewVisible, Converter={StaticResource BoolToVisibilityConverter}}"> + + Visibility="{Binding ShowPreviewImage}">