Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -43,8 +42,11 @@ internal AbstractPluginEnvironment(List<PluginMetadata> pluginMetadataList, Plug

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))
{
Expand All @@ -56,24 +58,55 @@ internal IEnumerable<PluginPair> Setup()
}

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
{
Expand All @@ -86,7 +119,7 @@ internal IEnumerable<PluginPair> Setup()
}
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");
Expand All @@ -99,13 +132,11 @@ internal IEnumerable<PluginPair> Setup()

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);
Expand All @@ -126,7 +157,7 @@ private IEnumerable<PluginPair> SetPathForPluginPairs(string filePath, string la
return pluginPairs;
}

private string GetFileFromDialog(string title, string filter = "")
private static string GetFileFromDialog(string title, string filter = "")
{
var dlg = new OpenFileDialog
{
Expand All @@ -140,7 +171,6 @@ private string GetFileFromDialog(string title, string filter = "")

var result = dlg.ShowDialog();
return result == DialogResult.OK ? dlg.FileName : string.Empty;

}

/// <summary>
Expand Down Expand Up @@ -183,31 +213,33 @@ public static void PreStartPluginExecutablePathUpdate(Settings settings)
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);
}
Expand All @@ -217,8 +249,8 @@ 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}";
var executablePathSubstring = filePath[(index + DataLocation.PluginEnvironments.Length)..];
return $"{DataLocation.PluginEnvironmentsPath}{executablePathSubstring}";
}
}
}
Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -22,7 +22,11 @@ internal class PythonEnvironment : AbstractPluginEnvironment

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) { }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.IO;
using Droplex;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin;
using System.IO;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin.SharedCommands;

namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
Expand All @@ -19,7 +19,11 @@ internal class TypeScriptEnvironment : AbstractPluginEnvironment
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) { }

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.IO;
using Droplex;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Infrastructure.UserSettings;
using Flow.Launcher.Plugin.SharedCommands;
using Flow.Launcher.Plugin;
using System.IO;
using Flow.Launcher.Core.Plugin;
using Flow.Launcher.Plugin.SharedCommands;

namespace Flow.Launcher.Core.ExternalPlugins.Environments
{
Expand All @@ -19,7 +19,11 @@ internal class TypeScriptV2Environment : AbstractPluginEnvironment
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) { }

Expand Down
11 changes: 5 additions & 6 deletions Flow.Launcher.Core/Plugin/PluginManager.cs
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down
5 changes: 5 additions & 0 deletions Flow.Launcher/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ private async void OnStartup(object sender, StartupEventArgs e)
{
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();
Expand Down
5 changes: 5 additions & 0 deletions Flow.Launcher/Languages/en.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
15 changes: 9 additions & 6 deletions Plugins/Flow.Launcher.Plugin.Sys/ThemeSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,37 @@ public class ThemeSelector
{
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

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)
Expand Down
Loading