diff --git a/.cm/gitstream.cm b/.cm/gitstream.cm new file mode 100644 index 000000000..c372f5a6c --- /dev/null +++ b/.cm/gitstream.cm @@ -0,0 +1,79 @@ +# -*- mode: yaml -*- +# This example configuration for provides basic automations to get started with gitStream. +# View the gitStream quickstart for more examples: https://docs.gitstream.cm/examples/ +manifest: + version: 1.0 + + +automations: + # Add a label that indicates how many minutes it will take to review the PR. + estimated_time_to_review: + if: + - true + run: + - action: add-label@v1 + args: + label: "{{ calc.etr }} min review" + color: {{ colors.red if (calc.etr >= 20) else ( colors.yellow if (calc.etr >= 5) else colors.green ) }} + # Post a comment that lists the best experts for the files that were modified. + explain_code_experts: + if: + - true + run: + - action: explain-code-experts@v1 + args: + gt: 10 + # Post a comment that indicates what percentage of the PR is new code. + percent_new_code: + if: + - true + run: + - action: add-comment@v1 + args: + comment: | + This PR is {{ changes.ratio }}% new code. + # Post a comment that request changes for a PR that contains a TODO statement. + review_todo_comments: + if: + - {{ source.diff.files | matchDiffLines(regex=r/^[+].*(TODO)|(todo)/) | some }} + run: + - action: request-changes@v1 + args: + comment: | + This PR contains a TODO statement. Please check to see if they should be removed. + # Post a comment that request a before and after screenshot + request_screenshot: + # Triggered for PRs that lack an image file or link to an image in the PR description + if: + - {{ not (has.screenshot_link or has.image_uploaded) }} + run: + - action: add-comment@v1 + args: + comment: | + Be a legend :trophy: by adding a before and after screenshot of the changes you made, especially if they are around UI/UX. + + +# +----------------------------------------------------------------------------+ +# | Custom Expressions | +# | https://docs.gitstream.cm/how-it-works/#custom-expressions | +# +----------------------------------------------------------------------------+ + +calc: + etr: {{ branch | estimatedReviewTime }} + +colors: + red: 'b60205' + yellow: 'fbca04' + green: '0e8a16' + +changes: + # Sum all the lines added/edited in the PR + additions: {{ branch.diff.files_metadata | map(attr='additions') | sum }} + # Sum all the line removed in the PR + deletions: {{ branch.diff.files_metadata | map(attr='deletions') | sum }} + # Calculate the ratio of new code + ratio: {{ (changes.additions / (changes.additions + changes.deletions)) * 100 | round(2) }} + +has: + screenshot_link: {{ pr.description | includes(regex=r/!\[.*\]\(.*(jpg|svg|png|gif|psd).*\)/) }} + image_uploaded: {{ pr.description | includes(regex=r//) }} \ No newline at end of file diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 00cc67ea0..a36a6af3e 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -3,3 +3,6 @@ https ssh ubuntu runcount +Firefox +Português +Português (Brasil) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index c4b1ee849..6ba41e410 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -74,6 +74,7 @@ WCA_ACCENT_POLICY HGlobal dopusrt firefox +Firefox msedge svgc ime @@ -97,6 +98,8 @@ Português Português (Brasil) Italiano Slovenský +quicklook +Tiếng Việt Droplex Preinstalled errormetadatafile diff --git a/.github/workflows/gitstream.yml b/.github/workflows/gitstream.yml new file mode 100644 index 000000000..0916572df --- /dev/null +++ b/.github/workflows/gitstream.yml @@ -0,0 +1,49 @@ +# Code generated by gitStream GitHub app - DO NOT EDIT + +name: gitStream workflow automation +run-name: | + /:\ gitStream: PR #${{ fromJSON(fromJSON(github.event.inputs.client_payload)).pullRequestNumber }} from ${{ github.event.inputs.full_repository }} + +on: + workflow_dispatch: + inputs: + client_payload: + description: The Client payload + required: true + full_repository: + description: the repository name include the owner in `owner/repo_name` format + required: true + head_ref: + description: the head sha + required: true + base_ref: + description: the base ref + required: true + installation_id: + description: the installation id + required: false + resolver_url: + description: the resolver url to pass results to + required: true + resolver_token: + description: Optional resolver token for resolver service + required: false + default: '' + +jobs: + gitStream: + timeout-minutes: 5 + runs-on: ubuntu-latest + name: gitStream workflow automation + steps: + - name: Evaluate Rules + uses: linear-b/gitstream-github-action@v2 + id: rules-engine + with: + full_repository: ${{ github.event.inputs.full_repository }} + head_ref: ${{ github.event.inputs.head_ref }} + base_ref: ${{ github.event.inputs.base_ref }} + client_payload: ${{ github.event.inputs.client_payload }} + installation_id: ${{ github.event.inputs.installation_id }} + resolver_url: ${{ github.event.inputs.resolver_url }} + resolver_token: ${{ github.event.inputs.resolver_token }} diff --git a/.github/workflows/pr_assignee.yml b/.github/workflows/pr_assignee.yml new file mode 100644 index 000000000..af6daff02 --- /dev/null +++ b/.github/workflows/pr_assignee.yml @@ -0,0 +1,19 @@ +name: Assign PR to creator + +# Due to GitHub token limitation, only able to assign org members not authors from forks. +# https://github.com/thomaseizinger/assign-pr-creator-action/issues/3 + +on: + pull_request: + types: [opened] + branches-ignore: + - l10n_dev + +jobs: + automation: + runs-on: ubuntu-latest + steps: + - name: Assign PR to creator + uses: thomaseizinger/assign-pr-creator-action@v1.0.0 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pr_milestone.yml b/.github/workflows/pr_milestone.yml new file mode 100644 index 000000000..b343a39cc --- /dev/null +++ b/.github/workflows/pr_milestone.yml @@ -0,0 +1,19 @@ +name: Set Milestone + +# Assigns the earliest created milestone that matches the below glob pattern. + +on: + pull_request: + types: [opened] + +jobs: + automation: + runs-on: ubuntu-latest + + steps: + - name: set-milestone + uses: andrefcdias/add-to-milestone@v1.3.0 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + milestone: "+([0-9]).+([0-9]).+([0-9])" + use-expression: true diff --git a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs index 64c4cd627..79d6d7605 100644 --- a/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs +++ b/Flow.Launcher.Core/ExternalPlugins/UserPlugin.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Flow.Launcher.Core.ExternalPlugins { @@ -13,9 +13,11 @@ namespace Flow.Launcher.Core.ExternalPlugins public string Website { get; set; } public string UrlDownload { get; set; } public string UrlSourceCode { get; set; } + public string LocalInstallPath { get; set; } public string IcoPath { get; set; } public DateTime? LatestReleaseDate { get; set; } public DateTime? DateAdded { get; set; } + public bool IsFromLocalInstallPath => !string.IsNullOrEmpty(LocalInstallPath); } } diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs index 46c72624a..5a6633525 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPluginV2.cs @@ -139,11 +139,20 @@ namespace Flow.Launcher.Core.Plugin return Task.CompletedTask; } - public virtual ValueTask DisposeAsync() + public virtual async ValueTask DisposeAsync() { - RPC?.Dispose(); - ErrorStream?.Dispose(); - return ValueTask.CompletedTask; + try + { + await RPC.InvokeAsync("close"); + } + catch (RemoteMethodNotFoundException e) + { + } + finally + { + RPC?.Dispose(); + ErrorStream?.Dispose(); + } } } } diff --git a/Flow.Launcher.Core/Plugin/PluginManager.cs b/Flow.Launcher.Core/Plugin/PluginManager.cs index 7f4d4ea35..a827e9e7a 100644 --- a/Flow.Launcher.Core/Plugin/PluginManager.cs +++ b/Flow.Launcher.Core/Plugin/PluginManager.cs @@ -1,4 +1,4 @@ -using Flow.Launcher.Core.ExternalPlugins; +using Flow.Launcher.Core.ExternalPlugins; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -90,6 +90,48 @@ namespace Flow.Launcher.Core.Plugin }).ToArray()); } + public static async Task OpenExternalPreviewAsync(string path, bool sendFailToast = true) + { + await Task.WhenAll(AllPlugins.Select(plugin => plugin.Plugin switch + { + IAsyncExternalPreview p => p.OpenPreviewAsync(path, sendFailToast), + _ => Task.CompletedTask, + }).ToArray()); + } + + public static async Task CloseExternalPreviewAsync() + { + await Task.WhenAll(AllPlugins.Select(plugin => plugin.Plugin switch + { + IAsyncExternalPreview p => p.ClosePreviewAsync(), + _ => Task.CompletedTask, + }).ToArray()); + } + + public static async Task SwitchExternalPreviewAsync(string path, bool sendFailToast = true) + { + await Task.WhenAll(AllPlugins.Select(plugin => plugin.Plugin switch + { + IAsyncExternalPreview p => p.SwitchPreviewAsync(path, sendFailToast), + _ => Task.CompletedTask, + }).ToArray()); + } + + public static bool UseExternalPreview() + { + return GetPluginsForInterface().Any(x => !x.Metadata.Disabled); + } + + public static bool AllowAlwaysPreview() + { + var plugin = GetPluginsForInterface().FirstOrDefault(x => !x.Metadata.Disabled); + + if (plugin is null) + return false; + + return ((IAsyncExternalPreview)plugin.Plugin).AllowAlwaysPreview(); + } + static PluginManager() { // validate user directory @@ -380,7 +422,8 @@ namespace Flow.Launcher.Core.Plugin /// - /// Update a plugin to new version, from a zip file. Will Delete zip after updating. + /// Update a plugin to new version, from a zip file. By default will remove the zip file if update is via url, + /// unless it's a local path installation /// public static void UpdatePlugin(PluginMetadata existingVersion, UserPlugin newVersion, string zipFilePath) { @@ -390,11 +433,11 @@ namespace Flow.Launcher.Core.Plugin } /// - /// Install a plugin. Will Delete zip after updating. + /// Install a plugin. By default will remove the zip file if installation is from url, unless it's a local path installation /// public static void InstallPlugin(UserPlugin plugin, string zipFilePath) { - InstallPlugin(plugin, zipFilePath, true); + InstallPlugin(plugin, zipFilePath, checkModified: true); } /// @@ -420,7 +463,9 @@ namespace Flow.Launcher.Core.Plugin // Unzip plugin files to temp folder var tempFolderPluginPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); System.IO.Compression.ZipFile.ExtractToDirectory(zipFilePath, tempFolderPluginPath); - File.Delete(zipFilePath); + + if(!plugin.IsFromLocalInstallPath) + File.Delete(zipFilePath); var pluginFolderPath = GetContainingFolderPathAfterUnzip(tempFolderPluginPath); diff --git a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs index a14baf271..bae263157 100644 --- a/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs +++ b/Flow.Launcher.Core/Plugin/ProcessStreamPluginV2.cs @@ -82,10 +82,10 @@ namespace Flow.Launcher.Core.Plugin public override async ValueTask DisposeAsync() { + await base.DisposeAsync(); ClientProcess.Kill(true); await ClientProcess.WaitForExitAsync(); ClientProcess.Dispose(); - await base.DisposeAsync(); } } } diff --git a/Flow.Launcher.Core/Resource/AvailableLanguages.cs b/Flow.Launcher.Core/Resource/AvailableLanguages.cs index b6d394d11..46c1ecb54 100644 --- a/Flow.Launcher.Core/Resource/AvailableLanguages.cs +++ b/Flow.Launcher.Core/Resource/AvailableLanguages.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; namespace Flow.Launcher.Core.Resource { @@ -27,6 +27,8 @@ namespace Flow.Launcher.Core.Resource public static Language Turkish = new Language("tr", "Türkçe"); public static Language Czech = new Language("cs", "čeština"); public static Language Arabic = new Language("ar", "اللغة العربية"); + public static Language Vietnamese = new Language("vi-vn", "Tiếng Việt"); + public static List GetAvailableLanguages() { @@ -54,7 +56,8 @@ namespace Flow.Launcher.Core.Resource Slovak, Turkish, Czech, - Arabic + Arabic, + Vietnamese }; return languages; } diff --git a/Flow.Launcher.Core/Resource/Internationalization.cs b/Flow.Launcher.Core/Resource/Internationalization.cs index acc693ed5..06eb868b8 100644 --- a/Flow.Launcher.Core/Resource/Internationalization.cs +++ b/Flow.Launcher.Core/Resource/Internationalization.cs @@ -96,13 +96,10 @@ namespace Flow.Launcher.Core.Resource { LoadLanguage(language); } - // Culture of this thread - // Use CreateSpecificCulture to preserve possible user-override settings in Windows + // Culture of main thread + // Use CreateSpecificCulture to preserve possible user-override settings in Windows, if Flow's language culture is the same as Windows's CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture(language.LanguageCode); CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture; - // App domain - CultureInfo.DefaultThreadCurrentCulture = CultureInfo.CreateSpecificCulture(language.LanguageCode); - CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.DefaultThreadCurrentCulture; // Raise event after culture is set Settings.Language = language.LanguageCode; @@ -193,7 +190,7 @@ namespace Flow.Launcher.Core.Resource { p.Metadata.Name = pluginI18N.GetTranslatedPluginTitle(); p.Metadata.Description = pluginI18N.GetTranslatedPluginDescription(); - pluginI18N.OnCultureInfoChanged(CultureInfo.DefaultThreadCurrentCulture); + pluginI18N.OnCultureInfoChanged(CultureInfo.CurrentCulture); } catch (Exception e) { diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 96338cf6a..c3a3e9891 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Xml; using System.Runtime.InteropServices; using System.Windows; using System.Windows.Controls; @@ -17,6 +18,10 @@ namespace Flow.Launcher.Core.Resource { public class Theme { + private const string ThemeMetadataNamePrefix = "Name:"; + private const string ThemeMetadataIsDarkPrefix = "IsDark:"; + private const string ThemeMetadataHasBlurPrefix = "HasBlur:"; + private const int ShadowExtraMargin = 32; private readonly List _themeDirectories = new List(); @@ -79,14 +84,14 @@ namespace Flow.Launcher.Core.Resource { if (string.IsNullOrEmpty(path)) throw new DirectoryNotFoundException("Theme path can't be found <{path}>"); - + // reload all resources even if the theme itself hasn't changed in order to pickup changes // to things like fonts UpdateResourceDictionary(GetResourceDictionary(theme)); - + Settings.Theme = theme; - + //always allow re-loading default theme, in case of failure of switching to a new theme from default theme if (_oldTheme != theme || theme == defaultTheme) { @@ -148,7 +153,7 @@ namespace Flow.Launcher.Core.Resource public ResourceDictionary GetResourceDictionary(string theme) { var dict = GetThemeResourceDictionary(theme); - + if (dict["QueryBoxStyle"] is Style queryBoxStyle && dict["QuerySuggestionBoxStyle"] is Style querySuggestionBoxStyle) { @@ -176,8 +181,6 @@ namespace Flow.Launcher.Core.Resource } if (dict["ItemTitleStyle"] is Style resultItemStyle && - dict["ItemSubTitleStyle"] is Style resultSubItemStyle && - dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle && dict["ItemTitleSelectedStyle"] is Style resultItemSelectedStyle && dict["ItemHotkeyStyle"] is Style resultHotkeyItemStyle && dict["ItemHotkeySelectedStyle"] is Style resultHotkeyItemSelectedStyle) @@ -189,9 +192,25 @@ namespace Flow.Launcher.Core.Resource Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch }; Array.ForEach( - new[] { resultItemStyle, resultSubItemStyle, resultItemSelectedStyle, resultSubItemSelectedStyle, resultHotkeyItemStyle, resultHotkeyItemSelectedStyle }, o + new[] { resultItemStyle, resultItemSelectedStyle, resultHotkeyItemStyle, resultHotkeyItemSelectedStyle }, o => Array.ForEach(setters, p => o.Setters.Add(p))); } + + if ( + dict["ItemSubTitleStyle"] is Style resultSubItemStyle && + dict["ItemSubTitleSelectedStyle"] is Style resultSubItemSelectedStyle) + { + Setter fontFamily = new Setter(TextBlock.FontFamilyProperty, new FontFamily(Settings.ResultSubFont)); + Setter fontStyle = new Setter(TextBlock.FontStyleProperty, FontHelper.GetFontStyleFromInvariantStringOrNormal(Settings.ResultSubFontStyle)); + Setter fontWeight = new Setter(TextBlock.FontWeightProperty, FontHelper.GetFontWeightFromInvariantStringOrNormal(Settings.ResultSubFontWeight)); + Setter fontStretch = new Setter(TextBlock.FontStretchProperty, FontHelper.GetFontStretchFromInvariantStringOrNormal(Settings.ResultSubFontStretch)); + + Setter[] setters = { fontFamily, fontStyle, fontWeight, fontStretch }; + Array.ForEach( + new[] { resultSubItemStyle,resultSubItemSelectedStyle}, o + => Array.ForEach(setters, p => o.Setters.Add(p))); + } + /* Ignore Theme Window Width and use setting */ var windowStyle = dict["WindowStyle"] as Style; var width = Settings.WindowSize; @@ -205,17 +224,53 @@ namespace Flow.Launcher.Core.Resource return GetResourceDictionary(Settings.Theme); } - public List LoadAvailableThemes() + public List LoadAvailableThemes() { - List themes = new List(); + List themes = new List(); foreach (var themeDirectory in _themeDirectories) { - themes.AddRange( - Directory.GetFiles(themeDirectory) - .Where(filePath => filePath.EndsWith(Extension) && !filePath.EndsWith("Base.xaml")) - .ToList()); + var filePaths = Directory + .GetFiles(themeDirectory) + .Where(filePath => filePath.EndsWith(Extension) && !filePath.EndsWith("Base.xaml")) + .Select(GetThemeDataFromPath); + themes.AddRange(filePaths); } - return themes.OrderBy(o => o).ToList(); + + return themes.OrderBy(o => o.Name).ToList(); + } + + private ThemeData GetThemeDataFromPath(string path) + { + using var reader = XmlReader.Create(path); + reader.Read(); + + var extensionlessName = Path.GetFileNameWithoutExtension(path); + + if (reader.NodeType is not XmlNodeType.Comment) + return new ThemeData(extensionlessName, extensionlessName); + + var commentLines = reader.Value.Trim().Split('\n').Select(v => v.Trim()); + + var name = extensionlessName; + bool? isDark = null; + bool? hasBlur = null; + foreach (var line in commentLines) + { + if (line.StartsWith(ThemeMetadataNamePrefix, StringComparison.OrdinalIgnoreCase)) + { + name = line.Remove(0, ThemeMetadataNamePrefix.Length).Trim(); + } + else if (line.StartsWith(ThemeMetadataIsDarkPrefix, StringComparison.OrdinalIgnoreCase)) + { + isDark = bool.Parse(line.Remove(0, ThemeMetadataIsDarkPrefix.Length).Trim()); + } + else if (line.StartsWith(ThemeMetadataHasBlurPrefix, StringComparison.OrdinalIgnoreCase)) + { + hasBlur = bool.Parse(line.Remove(0, ThemeMetadataHasBlurPrefix.Length).Trim()); + } + } + + return new ThemeData(extensionlessName, name, isDark, hasBlur); } private string GetThemePath(string themeName) @@ -393,5 +448,7 @@ namespace Flow.Launcher.Core.Resource Marshal.FreeHGlobal(accentPtr); } #endregion + + public record ThemeData(string FileNameWithoutExtension, string Name, bool? IsDark = null, bool? HasBlur = null); } } diff --git a/Flow.Launcher.Infrastructure/Constant.cs b/Flow.Launcher.Infrastructure/Constant.cs index 7a95b52d5..8a95ee79f 100644 --- a/Flow.Launcher.Infrastructure/Constant.cs +++ b/Flow.Launcher.Infrastructure/Constant.cs @@ -7,6 +7,7 @@ namespace Flow.Launcher.Infrastructure public static class Constant { public const string FlowLauncher = "Flow.Launcher"; + public const string FlowLauncherFullName = "Flow Launcher"; public const string Plugins = "Plugins"; public const string PluginMetadataFileName = "plugin.json"; diff --git a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj index 831091732..7d6448c43 100644 --- a/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj +++ b/Flow.Launcher.Infrastructure/Flow.Launcher.Infrastructure.csproj @@ -49,7 +49,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -57,8 +57,6 @@ - - diff --git a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs index 937059436..25bc75a56 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -6,7 +6,7 @@ using System.Windows.Input; namespace Flow.Launcher.Infrastructure.Hotkey { - public class HotkeyModel + public record struct HotkeyModel { public bool Alt { get; set; } public bool Shift { get; set; } @@ -17,8 +17,7 @@ namespace Flow.Launcher.Infrastructure.Hotkey private static readonly Dictionary specialSymbolDictionary = new Dictionary { - {Key.Space, "Space"}, - {Key.Oem3, "~"} + { Key.Space, "Space" }, { Key.Oem3, "~" } }; public ModifierKeys ModifierKeys @@ -30,18 +29,22 @@ namespace Flow.Launcher.Infrastructure.Hotkey { modifierKeys |= ModifierKeys.Alt; } + if (Shift) { modifierKeys |= ModifierKeys.Shift; } + if (Win) { modifierKeys |= ModifierKeys.Windows; } + if (Ctrl) { modifierKeys |= ModifierKeys.Control; } + return modifierKeys; } } @@ -66,31 +69,37 @@ namespace Flow.Launcher.Infrastructure.Hotkey { return; } + List keys = hotkeyString.Replace(" ", "").Split('+').ToList(); if (keys.Contains("Alt")) { Alt = true; keys.Remove("Alt"); } + if (keys.Contains("Shift")) { Shift = true; keys.Remove("Shift"); } + if (keys.Contains("Win")) { Win = true; keys.Remove("Win"); } + if (keys.Contains("Ctrl")) { Ctrl = true; keys.Remove("Ctrl"); } + if (keys.Count == 1) { string charKey = keys[0]; - KeyValuePair? specialSymbolPair = specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey); + KeyValuePair? specialSymbolPair = + specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey); if (specialSymbolPair.Value.Value != null) { CharKey = specialSymbolPair.Value.Key; @@ -103,7 +112,6 @@ namespace Flow.Launcher.Infrastructure.Hotkey } catch (ArgumentException) { - } } } @@ -111,33 +119,39 @@ namespace Flow.Launcher.Infrastructure.Hotkey public override string ToString() { - List keys = new List(); - if (Ctrl) + return string.Join(" + ", EnumerateDisplayKeys()); + } + + public IEnumerable EnumerateDisplayKeys() + { + if (Ctrl && CharKey is not (Key.LeftCtrl or Key.RightCtrl)) { - keys.Add("Ctrl"); + yield return "Ctrl"; } - if (Alt) + + if (Alt && CharKey is not (Key.LeftAlt or Key.RightAlt)) { - keys.Add("Alt"); + yield return "Alt"; } - if (Shift) + + if (Shift && CharKey is not (Key.LeftShift or Key.RightShift)) { - keys.Add("Shift"); + yield return "Shift"; } - if (Win) + + if (Win && CharKey is not (Key.LWin or Key.RWin)) { - keys.Add("Win"); + yield return "Win"; } if (CharKey != Key.None) { - keys.Add(specialSymbolDictionary.ContainsKey(CharKey) - ? specialSymbolDictionary[CharKey] - : CharKey.ToString()); + yield return specialSymbolDictionary.TryGetValue(CharKey, out var value) + ? value + : CharKey.ToString(); } - return string.Join(" + ", keys); } - + /// /// Validate hotkey /// @@ -164,11 +178,13 @@ namespace Flow.Launcher.Infrastructure.Hotkey { KeyGesture keyGesture = new KeyGesture(CharKey, ModifierKeys); } - catch (System.Exception e) when (e is NotSupportedException || e is InvalidEnumArgumentException) + catch (System.Exception e) when + (e is NotSupportedException || e is InvalidEnumArgumentException) { return false; } } + if (ModifierKeys == ModifierKeys.None) { return !IsPrintableCharacter(CharKey); @@ -206,18 +222,6 @@ namespace Flow.Launcher.Infrastructure.Hotkey key == Key.Decimal; } - public override bool Equals(object obj) - { - if (obj is HotkeyModel other) - { - return ModifierKeys == other.ModifierKeys && CharKey == other.CharKey; - } - else - { - return false; - } - } - public override int GetHashCode() { return HashCode.Combine(ModifierKeys, CharKey); diff --git a/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs b/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs new file mode 100644 index 000000000..448a70d19 --- /dev/null +++ b/Flow.Launcher.Infrastructure/Hotkey/IHotkeySettings.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Flow.Launcher.Infrastructure.Hotkey; + +/// +/// Interface that you should implement in your settings class to be able to pass it to +/// Flow.Launcher.HotkeyControlDialog. It allows the dialog to display the hotkeys that have already been +/// registered, and optionally provide a way to unregister them. +/// +public interface IHotkeySettings +{ + /// + /// A list of hotkeys that have already been registered. The dialog will display these hotkeys and provide a way to + /// unregister them. + /// + public List RegisteredHotkeys { get; } +} diff --git a/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs new file mode 100644 index 000000000..b6a10fc30 --- /dev/null +++ b/Flow.Launcher.Infrastructure/Hotkey/RegisteredHotkeyData.cs @@ -0,0 +1,119 @@ +using System; + +namespace Flow.Launcher.Infrastructure.Hotkey; + +#nullable enable + +/// +/// Represents a hotkey that has been registered. Used in Flow.Launcher.HotkeyControlDialog via +/// and to display errors if user tries to register a hotkey +/// that has already been registered, and optionally provides a way to unregister the hotkey. +/// +public record RegisteredHotkeyData +{ + /// + /// representation of this hotkey. + /// + public HotkeyModel Hotkey { get; } + + /// + /// String key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + public string DescriptionResourceKey { get; } + + /// + /// Array of values that will replace {0}, {1}, {2}, etc. in the localized string found via + /// . + /// + public object?[] DescriptionFormatVariables { get; } = Array.Empty(); + + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that + /// this hotkey can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public Action? RemoveHotkey { get; } + + /// + /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in + /// descriptionResourceKey doesn't need any arguments for string.Format. If it does, + /// use one of the other constructors. + /// + /// + /// The hotkey this class will represent. + /// Example values: F1, Ctrl+Shift+Enter + /// + /// + /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey + /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public RegisteredHotkeyData(string hotkey, string descriptionResourceKey, Action? removeHotkey = null) + { + Hotkey = new HotkeyModel(hotkey); + DescriptionResourceKey = descriptionResourceKey; + RemoveHotkey = removeHotkey; + } + + /// + /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in + /// descriptionResourceKey needs exactly one argument for string.Format. + /// + /// + /// The hotkey this class will represent. + /// Example values: F1, Ctrl+Shift+Enter + /// + /// + /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + /// + /// The value that will replace {0} in the localized string found via description. + /// + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey + /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public RegisteredHotkeyData( + string hotkey, string descriptionResourceKey, object? descriptionFormatVariable, Action? removeHotkey = null + ) + { + Hotkey = new HotkeyModel(hotkey); + DescriptionResourceKey = descriptionResourceKey; + DescriptionFormatVariables = new[] { descriptionFormatVariable }; + RemoveHotkey = removeHotkey; + } + + /// + /// Creates an instance of RegisteredHotkeyData. Assumes that the key specified in + /// needs multiple arguments for string.Format. + /// + /// + /// The hotkey this class will represent. + /// Example values: F1, Ctrl+Shift+Enter + /// + /// + /// The key in the localization dictionary that represents this hotkey. For example, ReloadPluginHotkey, + /// which represents the string "Reload Plugins Data" in en.xaml + /// + /// + /// Array of values that will replace {0}, {1}, {2}, etc. + /// in the localized string found via description. + /// + /// + /// An action that, when called, will unregister this hotkey. If it's null, it's assumed that this hotkey + /// can't be unregistered, and the "Overwrite" option will not appear in the hotkey dialog. + /// + public RegisteredHotkeyData( + string hotkey, string descriptionResourceKey, object?[] descriptionFormatVariables, Action? removeHotkey = null + ) + { + Hotkey = new HotkeyModel(hotkey); + DescriptionResourceKey = descriptionResourceKey; + DescriptionFormatVariables = descriptionFormatVariables; + RemoveHotkey = removeHotkey; + } +} diff --git a/Flow.Launcher.Infrastructure/Image/ImageCache.cs b/Flow.Launcher.Infrastructure/Image/ImageCache.cs index 55545b9a7..ddbab4ef0 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageCache.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageCache.cs @@ -3,33 +3,23 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows.Media; -using FastCache; -using FastCache.Services; +using BitFaster.Caching.Lfu; namespace Flow.Launcher.Infrastructure.Image { - public class ImageUsage - { - public int usage; - public ImageSource imageSource; - - public ImageUsage(int usage, ImageSource image) - { - this.usage = usage; - imageSource = image; - } - } - public class ImageCache { private const int MaxCached = 150; - public void Initialize(Dictionary<(string, bool), int> usage) + private ConcurrentLfu<(string, bool), ImageSource> CacheManager { get; set; } = new(MaxCached); + + public void Initialize(IEnumerable<(string, bool)> usage) { - foreach (var key in usage.Keys) + foreach (var key in usage) { - Cached.Save(key, new ImageUsage(usage[key], null), TimeSpan.MaxValue, MaxCached); + CacheManager.AddOrUpdate(key, null); } } @@ -37,48 +27,42 @@ namespace Flow.Launcher.Infrastructure.Image { get { - if (!Cached.TryGet((path, isFullImage), out var value)) - { - return null; - } - - value.Value.usage++; - return value.Value.imageSource; + return CacheManager.TryGet((path, isFullImage), out var value) ? value : null; } set { - if (Cached.TryGet((path, isFullImage), out var cached)) - { - cached.Value.imageSource = value; - cached.Value.usage++; - } - - Cached.Save((path, isFullImage), new ImageUsage(0, value), TimeSpan.MaxValue, - MaxCached); + CacheManager.AddOrUpdate((path, isFullImage), value); } } + public async ValueTask GetOrAddAsync(string key, + Func<(string, bool), Task> valueFactory, + bool isFullImage = false) + { + return await CacheManager.GetOrAddAsync((key, isFullImage), valueFactory); + } + public bool ContainsKey(string key, bool isFullImage) { - return Cached.TryGet((key, isFullImage), out _); + return CacheManager.TryGet((key, isFullImage), out _); } public bool TryGetValue(string key, bool isFullImage, out ImageSource image) { - if (Cached.TryGet((key, isFullImage), out var value)) + if (CacheManager.TryGet((key, isFullImage), out var value)) { - image = value.Value.imageSource; - value.Value.usage++; + image = value; return image != null; } + image = null; return false; } public int CacheSize() { - return CacheManager.TotalCount<(string, bool), ImageUsage>(); + return CacheManager.Count; } /// @@ -86,14 +70,14 @@ namespace Flow.Launcher.Infrastructure.Image /// public int UniqueImagesInCache() { - return CacheManager.EnumerateEntries<(string, bool), ImageUsage>().Select(x => x.Value.imageSource) + return CacheManager.Select(x => x.Value) .Distinct() .Count(); } - public IEnumerable> EnumerateEntries() + public IEnumerable> EnumerateEntries() { - return CacheManager.EnumerateEntries<(string, bool), ImageUsage>(); + return CacheManager; } } } diff --git a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs index 75c2a4ec9..612f495be 100644 --- a/Flow.Launcher.Infrastructure/Image/ImageLoader.cs +++ b/Flow.Launcher.Infrastructure/Image/ImageLoader.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using System.Windows.Documents; using System.Windows.Media; using System.Windows.Media.Imaging; using Flow.Launcher.Infrastructure.Logger; @@ -17,7 +18,7 @@ namespace Flow.Launcher.Infrastructure.Image { private static readonly ImageCache ImageCache = new(); private static SemaphoreSlim storageLock { get; } = new SemaphoreSlim(1, 1); - private static BinaryStorage> _storage; + private static BinaryStorage> _storage; private static readonly ConcurrentDictionary GuidToKey = new(); private static IImageHashGenerator _hashGenerator; private static readonly bool EnableImageHash = true; @@ -31,12 +32,12 @@ namespace Flow.Launcher.Infrastructure.Image public static async Task InitializeAsync() { - _storage = new BinaryStorage>("Image"); + _storage = new BinaryStorage>("Image"); _hashGenerator = new ImageHashGenerator(); var usage = await LoadStorageToConcurrentDictionaryAsync(); - ImageCache.Initialize(usage.ToDictionary(x => x.Key, x => x.Value)); + ImageCache.Initialize(usage); foreach (var icon in new[] { Constant.DefaultIcon, Constant.MissingImgIcon }) { @@ -49,7 +50,7 @@ namespace Flow.Launcher.Infrastructure.Image { await Stopwatch.NormalAsync("|ImageLoader.Initialize|Preload images cost", async () => { - foreach (var ((path, isFullImage), _) in usage) + foreach (var (path, isFullImage) in usage) { await LoadAsync(path, isFullImage); } @@ -66,9 +67,8 @@ namespace Flow.Launcher.Infrastructure.Image try { await _storage.SaveAsync(ImageCache.EnumerateEntries() - .ToDictionary( - x => x.Key, - x => x.Value.usage)); + .Select(x => x.Key) + .ToList()); } finally { @@ -76,14 +76,12 @@ namespace Flow.Launcher.Infrastructure.Image } } - private static async Task> LoadStorageToConcurrentDictionaryAsync() + private static async Task> LoadStorageToConcurrentDictionaryAsync() { await storageLock.WaitAsync(); try { - var loaded = await _storage.TryLoadAsync(new Dictionary<(string, bool), int>()); - - return new ConcurrentDictionary<(string, bool), int>(loaded); + return await _storage.TryLoadAsync(new List<(string, bool)>()); } finally { diff --git a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs index a679643fd..2a439b8cc 100644 --- a/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/BinaryStorage.cs @@ -67,7 +67,7 @@ namespace Flow.Launcher.Infrastructure.Storage } catch (System.Exception e) { - Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e); + // Log.Exception($"|BinaryStorage.Deserialize|Deserialize error for file <{FilePath}>", e); return defaultData; } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index 274f88dc6..0c7de10fd 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -4,13 +4,14 @@ using System.Collections.ObjectModel; using System.Drawing; using System.Text.Json.Serialization; using System.Windows; +using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Plugin; using Flow.Launcher.Plugin.SharedModels; using Flow.Launcher.ViewModel; namespace Flow.Launcher.Infrastructure.UserSettings { - public class Settings : BaseModel + public class Settings : BaseModel, IHotkeySettings { private string language = "en"; private string _theme = Constant.DefaultTheme; @@ -20,6 +21,18 @@ namespace Flow.Launcher.Infrastructure.UserSettings public bool ShowOpenResultHotkey { get; set; } = true; public double WindowSize { get; set; } = 580; public string PreviewHotkey { get; set; } = $"F1"; + public string AutoCompleteHotkey { get; set; } = $"{KeyConstant.Ctrl} + Tab"; + public string AutoCompleteHotkey2 { get; set; } = $""; + public string SelectNextItemHotkey { get; set; } = $"Tab"; + public string SelectNextItemHotkey2 { get; set; } = $""; + public string SelectPrevItemHotkey { get; set; } = $"Shift + Tab"; + public string SelectPrevItemHotkey2 { get; set; } = $""; + public string SelectNextPageHotkey { get; set; } = $"PageUp"; + public string SelectPrevPageHotkey { get; set; } = $"PageDown"; + public string OpenContextMenuHotkey { get; set; } = $"Ctrl+O"; + public string SettingWindowHotkey { get; set; } = $"Ctrl+I"; + public string CycleHistoryUpHotkey { get; set; } = $"{KeyConstant.Alt} + Up"; + public string CycleHistoryDownHotkey { get; set; } = $"{KeyConstant.Alt} + Down"; public string Language { @@ -42,7 +55,14 @@ namespace Flow.Launcher.Infrastructure.UserSettings OnPropertyChanged(nameof(MaxResultsToShow)); } } - public bool UseDropShadowEffect { get; set; } = false; + public bool UseDropShadowEffect { get; set; } = true; + + /* Appearance Settings. It should be separated from the setting later.*/ + public double WindowHeightSize { get; set; } = 42; + public double ItemHeightSize { get; set; } = 58; + public double QueryBoxFontSize { get; set; } = 20; + public double ResultItemFontSize { get; set; } = 16; + public double ResultSubItemFontSize { get; set; } = 13; public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name; public string QueryBoxFontStyle { get; set; } public string QueryBoxFontWeight { get; set; } @@ -51,6 +71,10 @@ namespace Flow.Launcher.Infrastructure.UserSettings public string ResultFontStyle { get; set; } public string ResultFontWeight { get; set; } public string ResultFontStretch { get; set; } + public string ResultSubFont { get; set; } = FontFamily.GenericSansSerif.Name; + public string ResultSubFontStyle { get; set; } + public string ResultSubFontWeight { get; set; } + public string ResultSubFontStretch { get; set; } public bool UseGlyphIcons { get; set; } = true; public bool UseAnimation { get; set; } = true; public bool UseSound { get; set; } = true; @@ -64,8 +88,8 @@ namespace Flow.Launcher.Infrastructure.UserSettings public double SettingWindowWidth { get; set; } = 1000; public double SettingWindowHeight { get; set; } = 700; - public double SettingWindowTop { get; set; } - public double SettingWindowLeft { get; set; } + public double? SettingWindowTop { get; set; } = null; + public double? SettingWindowLeft { get; set; } = null; public System.Windows.WindowState SettingWindowState { get; set; } = WindowState.Normal; public int CustomExplorerIndex { get; set; } = 0; @@ -161,35 +185,21 @@ namespace Flow.Launcher.Infrastructure.UserSettings /// when false Alphabet static service will always return empty results /// public bool ShouldUsePinyin { get; set; } = false; + public bool AlwaysPreview { get; set; } = false; + public bool AlwaysStartEn { get; set; } = false; + private SearchPrecisionScore _querySearchPrecision = SearchPrecisionScore.Regular; [JsonInclude, JsonConverter(typeof(JsonStringEnumConverter))] - public SearchPrecisionScore QuerySearchPrecision { get; private set; } = SearchPrecisionScore.Regular; - - [JsonIgnore] - public string QuerySearchPrecisionString + public SearchPrecisionScore QuerySearchPrecision { - get { return QuerySearchPrecision.ToString(); } + get => _querySearchPrecision; set { - try - { - var precisionScore = (SearchPrecisionScore)Enum - .Parse(typeof(SearchPrecisionScore), value); - - QuerySearchPrecision = precisionScore; - StringMatcher.Instance.UserSettingSearchPrecision = precisionScore; - } - catch (ArgumentException e) - { - Logger.Log.Exception(nameof(Settings), "Failed to load QuerySearchPrecisionString value from Settings file", e); - - QuerySearchPrecision = SearchPrecisionScore.Regular; - StringMatcher.Instance.UserSettingSearchPrecision = SearchPrecisionScore.Regular; - - throw; - } + _querySearchPrecision = value; + if (StringMatcher.Instance != null) + StringMatcher.Instance.UserSettingSearchPrecision = value; } } @@ -197,17 +207,18 @@ namespace Flow.Launcher.Infrastructure.UserSettings public double WindowLeft { get; set; } public double WindowTop { get; set; } - + /// /// Custom left position on selected monitor /// public double CustomWindowLeft { get; set; } = 0; - + /// /// Custom top position on selected monitor /// public double CustomWindowTop { get; set; } = 0; - + + public bool KeepMaxResults { get; set; } = false; public int MaxResultsToShow { get; set; } = 5; public int ActivateTimes { get; set; } @@ -219,7 +230,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings [JsonIgnore] public ObservableCollection BuiltinShortcuts { get; set; } = new() { - new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText), + new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText), new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath) }; @@ -243,7 +254,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings [JsonConverter(typeof(JsonStringEnumConverter))] public SearchWindowScreens SearchWindowScreen { get; set; } = SearchWindowScreens.Cursor; - + [JsonConverter(typeof(JsonStringEnumConverter))] public SearchWindowAligns SearchWindowAlign { get; set; } = SearchWindowAligns.Center; @@ -260,9 +271,99 @@ namespace Flow.Launcher.Infrastructure.UserSettings public AnimationSpeeds AnimationSpeed { get; set; } = AnimationSpeeds.Medium; public int CustomAnimationLength { get; set; } = 360; + [JsonIgnore] + public bool WMPInstalled { get; set; } = true; + // This needs to be loaded last by staying at the bottom public PluginsSettings PluginSettings { get; set; } = new PluginsSettings(); + + [JsonIgnore] + public List RegisteredHotkeys + { + get + { + var list = FixedHotkeys(); + + // Customizeable hotkeys + if(!string.IsNullOrEmpty(Hotkey)) + list.Add(new(Hotkey, "flowlauncherHotkey", () => Hotkey = "")); + if(!string.IsNullOrEmpty(PreviewHotkey)) + list.Add(new(PreviewHotkey, "previewHotkey", () => PreviewHotkey = "")); + if(!string.IsNullOrEmpty(AutoCompleteHotkey)) + list.Add(new(AutoCompleteHotkey, "autoCompleteHotkey", () => AutoCompleteHotkey = "")); + if(!string.IsNullOrEmpty(AutoCompleteHotkey2)) + list.Add(new(AutoCompleteHotkey2, "autoCompleteHotkey", () => AutoCompleteHotkey2 = "")); + if(!string.IsNullOrEmpty(SelectNextItemHotkey)) + list.Add(new(SelectNextItemHotkey, "SelectNextItemHotkey", () => SelectNextItemHotkey = "")); + if(!string.IsNullOrEmpty(SelectNextItemHotkey2)) + list.Add(new(SelectNextItemHotkey2, "SelectNextItemHotkey", () => SelectNextItemHotkey2 = "")); + if(!string.IsNullOrEmpty(SelectPrevItemHotkey)) + list.Add(new(SelectPrevItemHotkey, "SelectPrevItemHotkey", () => SelectPrevItemHotkey = "")); + if(!string.IsNullOrEmpty(SelectPrevItemHotkey2)) + list.Add(new(SelectPrevItemHotkey2, "SelectPrevItemHotkey", () => SelectPrevItemHotkey2 = "")); + if(!string.IsNullOrEmpty(SettingWindowHotkey)) + list.Add(new(SettingWindowHotkey, "SettingWindowHotkey", () => SettingWindowHotkey = "")); + if(!string.IsNullOrEmpty(OpenContextMenuHotkey)) + list.Add(new(OpenContextMenuHotkey, "OpenContextMenuHotkey", () => OpenContextMenuHotkey = "")); + if(!string.IsNullOrEmpty(SelectNextPageHotkey)) + list.Add(new(SelectNextPageHotkey, "SelectNextPageHotkey", () => SelectNextPageHotkey = "")); + if(!string.IsNullOrEmpty(SelectPrevPageHotkey)) + list.Add(new(SelectPrevPageHotkey, "SelectPrevPageHotkey", () => SelectPrevPageHotkey = "")); + if (!string.IsNullOrEmpty(CycleHistoryUpHotkey)) + list.Add(new(CycleHistoryUpHotkey, "CycleHistoryUpHotkey", () => CycleHistoryUpHotkey = "")); + if (!string.IsNullOrEmpty(CycleHistoryDownHotkey)) + list.Add(new(CycleHistoryDownHotkey, "CycleHistoryDownHotkey", () => CycleHistoryDownHotkey = "")); + + // Custom Query Hotkeys + foreach (var customPluginHotkey in CustomPluginHotkeys) + { + if (!string.IsNullOrEmpty(customPluginHotkey.Hotkey)) + list.Add(new(customPluginHotkey.Hotkey, "customQueryHotkey", () => customPluginHotkey.Hotkey = "")); + } + + return list; + } + } + + private List FixedHotkeys() + { + return new List + { + new("Up", "HotkeyLeftRightDesc"), + new("Down", "HotkeyLeftRightDesc"), + new("Left", "HotkeyUpDownDesc"), + new("Right", "HotkeyUpDownDesc"), + new("Escape", "HotkeyESCDesc"), + new("F5", "ReloadPluginHotkey"), + new("Alt+Home", "HotkeySelectFirstResult"), + new("Alt+End", "HotkeySelectLastResult"), + new("Ctrl+R", "HotkeyRequery"), + new("Ctrl+H", "ToggleHistoryHotkey"), + new("Ctrl+OemCloseBrackets", "QuickWidthHotkey"), + new("Ctrl+OemOpenBrackets", "QuickWidthHotkey"), + new("Ctrl+OemPlus", "QuickHeightHotkey"), + new("Ctrl+OemMinus", "QuickHeightHotkey"), + new("Ctrl+Shift+Enter", "HotkeyCtrlShiftEnterDesc"), + new("Shift+Enter", "OpenContextMenuHotkey"), + new("Enter", "HotkeyRunDesc"), + new("Ctrl+Enter", "OpenContainFolderHotkey"), + new("Alt+Enter", "HotkeyOpenResult"), + new("Ctrl+F12", "ToggleGameModeHotkey"), + new("Ctrl+Shift+C", "CopyFilePathHotkey"), + + new($"{OpenResultModifiers}+D1", "HotkeyOpenResultN", 1), + new($"{OpenResultModifiers}+D2", "HotkeyOpenResultN", 2), + new($"{OpenResultModifiers}+D3", "HotkeyOpenResultN", 3), + new($"{OpenResultModifiers}+D4", "HotkeyOpenResultN", 4), + new($"{OpenResultModifiers}+D5", "HotkeyOpenResultN", 5), + new($"{OpenResultModifiers}+D6", "HotkeyOpenResultN", 6), + new($"{OpenResultModifiers}+D7", "HotkeyOpenResultN", 7), + new($"{OpenResultModifiers}+D8", "HotkeyOpenResultN", 8), + new($"{OpenResultModifiers}+D9", "HotkeyOpenResultN", 9), + new($"{OpenResultModifiers}+D0", "HotkeyOpenResultN", 10) + }; + } } public enum LastQueryMode @@ -278,7 +379,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings Light, Dark } - + public enum SearchWindowScreens { RememberLastLaunchLocation, @@ -287,7 +388,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings Primary, Custom } - + public enum SearchWindowAligns { Center, diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index cc89afbc2..7b0880b67 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -14,10 +14,10 @@ - 4.2.0 - 4.2.0 - 4.2.0 - 4.2.0 + 4.3.0 + 4.3.0 + 4.3.0 + 4.3.0 Flow.Launcher.Plugin Flow-Launcher MIT diff --git a/Flow.Launcher.Plugin/Interfaces/IAsyncExternalPreview.cs b/Flow.Launcher.Plugin/Interfaces/IAsyncExternalPreview.cs new file mode 100644 index 000000000..cc4f94c56 --- /dev/null +++ b/Flow.Launcher.Plugin/Interfaces/IAsyncExternalPreview.cs @@ -0,0 +1,40 @@ +using System.Threading.Tasks; + +namespace Flow.Launcher.Plugin +{ + /// + /// This interface is for plugins that wish to provide file preview (external preview) + /// via a third party app instead of the default preview. + /// + public interface IAsyncExternalPreview : IFeatures + { + /// + /// Method for opening/showing the preview. + /// + /// The file path to open the preview for + /// Whether to send a toast message notification on failure for the user + public Task OpenPreviewAsync(string path, bool sendFailToast = true); + + /// + /// Method for closing/hiding the preview. + /// + public Task ClosePreviewAsync(); + + /// + /// Method for switching the preview to the next file result. + /// This requires the external preview be already open/showing + /// + /// The file path to switch the preview for + /// Whether to send a toast message notification on failure for the user + public Task SwitchPreviewAsync(string path, bool sendFailToast = true); + + /// + /// Allows the preview plugin to override the AlwaysPreview setting. Typically useful if plugin's preview does not + /// fully work well with being shown together when the query window appears with results. + /// When AlwaysPreview setting is on and this is set to false, the preview will not be shown when query + /// window appears with results, instead the internal preview will be shown. + /// + /// + public bool AllowAlwaysPreview(); + } +} diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index fc6c5d185..9b42b1021 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -204,7 +204,7 @@ namespace Flow.Launcher.Plugin Score = Score, TitleHighlightData = TitleHighlightData, OriginQuery = OriginQuery, - PluginDirectory = PluginDirectory + PluginDirectory = PluginDirectory, }; } @@ -258,7 +258,7 @@ namespace Flow.Launcher.Plugin public string ProgressBarColor { get; set; } = "#26a0da"; /// - /// Contains data used to populate the the preview section of this result. + /// Contains data used to populate the preview section of this result. /// public PreviewInfo Preview { get; set; } = PreviewInfo.Default; @@ -270,12 +270,12 @@ namespace Flow.Launcher.Plugin /// /// Full image used for preview panel /// - public string PreviewImagePath { get; set; } + public string PreviewImagePath { get; set; } = null; /// /// Determines if the preview image should occupy the full width of the preview panel. /// - public bool IsMedia { get; set; } + public bool IsMedia { get; set; } = false; /// /// Result description text that is shown at the bottom of the preview panel. @@ -283,12 +283,17 @@ namespace Flow.Launcher.Plugin /// /// When a value is not set, the will be used. /// - public string Description { get; set; } + public string Description { get; set; } = null; /// /// Delegate to get the preview panel's image /// - public IconDelegate PreviewDelegate { get; set; } + public IconDelegate PreviewDelegate { get; set; } = null; + + /// + /// File path of the result. For third-party programs providing external preview. + /// + public string FilePath { get; set; } = null; /// /// Default instance of @@ -299,6 +304,7 @@ namespace Flow.Launcher.Plugin Description = null, IsMedia = false, PreviewDelegate = null, + FilePath = null, }; } } diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 4137ca8d0..dd8c4b112 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; #pragma warning disable IDE0005 using System.Windows; #pragma warning restore IDE0005 @@ -200,6 +201,24 @@ namespace Flow.Launcher.Plugin.SharedCommands } } + /// + /// This checks whether a given string is a zip file path. + /// By default does not check if the zip file actually exist on disk, can do so by + /// setting checkFileExists = true. + /// + public static bool IsZipFilePath(string querySearchString, bool checkFileExists = false) + { + if (IsLocationPathString(querySearchString) && querySearchString.Split('.').Last() == "zip") + { + if (checkFileExists) + return FileExists(querySearchString); + + return true; + } + + return false; + } + /// /// This checks whether a given string is a directory path or network location string. /// It does not check if location actually exists. diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index e9d37433f..80cb74729 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -191,11 +191,15 @@ namespace Flow.Launcher.Test.Plugins [TestCase(@"\c:\", false)] [TestCase(@"cc:\", false)] [TestCase(@"\\\SomeNetworkLocation\", false)] + [TestCase(@"\\SomeNetworkLocation\", true)] [TestCase("RandomFile", false)] [TestCase(@"c:\>*", true)] [TestCase(@"c:\>", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation", true)] + [TestCase(@"c:\SomeLocation\SomeOtherLocation\SomeFile.exe", true)] + [TestCase(@"\\SomeNetworkLocation\SomeFile.exe", true)] + public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult) { // When, Given diff --git a/Flow.Launcher.sln b/Flow.Launcher.sln index 13e98833a..e44b23232 100644 --- a/Flow.Launcher.sln +++ b/Flow.Launcher.sln @@ -54,6 +54,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Scripts\post_build.ps1 = Scripts\post_build.ps1 README.md = README.md SolutionAssemblyInfo.cs = SolutionAssemblyInfo.cs + Settings.XamlStyler = Settings.XamlStyler EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Flow.Launcher.Plugin.Shell", "Plugins\Flow.Launcher.Plugin.Shell\Flow.Launcher.Plugin.Shell.csproj", "{C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0}" diff --git a/Flow.Launcher/App.xaml b/Flow.Launcher/App.xaml index b8e2a1cfe..13e943c95 100644 --- a/Flow.Launcher/App.xaml +++ b/Flow.Launcher/App.xaml @@ -24,7 +24,8 @@ - + + diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index f4a17761f..765a1a559 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -62,6 +62,7 @@ namespace Flow.Launcher _settingsVM = new SettingWindowViewModel(_updater, _portable); _settings = _settingsVM.Settings; + _settings.WMPInstalled = WindowsMediaPlayerHelper.IsWindowsMediaPlayerInstalled(); AbstractPluginEnvironment.PreStartPluginExecutablePathUpdate(_settings); @@ -80,7 +81,7 @@ namespace Flow.Launcher await PluginManager.InitializePluginsAsync(API); await imageLoadertask; - + var window = new MainWindow(_settings, _mainVM); Log.Info($"|App.OnStartup|Dependencies Info:{ErrorReporting.DependenciesInfo()}"); @@ -88,12 +89,13 @@ namespace Flow.Launcher Current.MainWindow = window; Current.MainWindow.Title = Constant.FlowLauncher; - HotKeyMapper.Initialize(_mainVM); - // todo temp fix for instance code logic // load plugin before change language, because plugin language also needs be changed InternationalizationManager.Instance.Settings = _settings; InternationalizationManager.Instance.ChangeLanguage(_settings.Language); + + HotKeyMapper.Initialize(_mainVM); + // main windows needs initialized before theme change because of blur settings ThemeManager.Instance.Settings = _settings; ThemeManager.Instance.ChangeTheme(_settings.Theme); diff --git a/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs b/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs index fabd01a24..ed94771f0 100644 --- a/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs +++ b/Flow.Launcher/Converters/QuerySuggestionBoxConverter.cs @@ -44,7 +44,7 @@ public class QuerySuggestionBoxConverter : IMultiValueConverter // Check if Text will be larger than our QueryTextBox Typeface typeface = new Typeface(queryTextBox.FontFamily, queryTextBox.FontStyle, queryTextBox.FontWeight, queryTextBox.FontStretch); // TODO: Obsolete warning? - var ft = new FormattedText(queryTextBox.Text, CultureInfo.DefaultThreadCurrentCulture, System.Windows.FlowDirection.LeftToRight, typeface, queryTextBox.FontSize, Brushes.Black); + var ft = new FormattedText(queryTextBox.Text, CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, typeface, queryTextBox.FontSize, Brushes.Black); var offset = queryTextBox.Padding.Right; diff --git a/Flow.Launcher/CustomQueryHotkeySetting.xaml b/Flow.Launcher/CustomQueryHotkeySetting.xaml index 1113cb24d..068afda15 100644 --- a/Flow.Launcher/CustomQueryHotkeySetting.xaml +++ b/Flow.Launcher/CustomQueryHotkeySetting.xaml @@ -2,10 +2,12 @@ x:Class="Flow.Launcher.CustomQueryHotkeySetting" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:flowlauncher="clr-namespace:Flow.Launcher" Title="{DynamicResource customeQueryHotkeyTitle}" Width="530" Background="{DynamicResource PopuBGColor}" + DataContext="{Binding RelativeSource={RelativeSource Self}}" Foreground="{DynamicResource PopupTextColor}" Icon="Images\app.png" MouseDown="window_MouseDown" @@ -60,94 +62,76 @@ - - - - - - - + + + - - - - - - - - - - - - - - - - - - + diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index 3da66caeb..a42bde7c9 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -1,141 +1,204 @@ -using System; +#nullable enable + +using System.Collections.ObjectModel; using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; using System.Windows.Input; -using System.Windows.Media; using Flow.Launcher.Core.Resource; using Flow.Launcher.Helper; using Flow.Launcher.Infrastructure.Hotkey; -using Flow.Launcher.Plugin; -using System.Threading; namespace Flow.Launcher { - public partial class HotkeyControl : UserControl + public partial class HotkeyControl { - public HotkeyModel CurrentHotkey { get; private set; } - public bool CurrentHotkeyAvailable { get; private set; } + public IHotkeySettings HotkeySettings { + get { return (IHotkeySettings)GetValue(HotkeySettingsProperty); } + set { SetValue(HotkeySettingsProperty, value); } + } - public event EventHandler HotkeyChanged; + public static readonly DependencyProperty HotkeySettingsProperty = DependencyProperty.Register( + nameof(HotkeySettings), + typeof(IHotkeySettings), + typeof(HotkeyControl), + new PropertyMetadata() + ); + public string WindowTitle { + get { return (string)GetValue(WindowTitleProperty); } + set { SetValue(WindowTitleProperty, value); } + } + + public static readonly DependencyProperty WindowTitleProperty = DependencyProperty.Register( + nameof(WindowTitle), + typeof(string), + typeof(HotkeyControl), + new PropertyMetadata(string.Empty) + ); /// /// Designed for Preview Hotkey and KeyGesture. /// - public bool ValidateKeyGesture { get; set; } = false; + public static readonly DependencyProperty ValidateKeyGestureProperty = DependencyProperty.Register( + nameof(ValidateKeyGesture), + typeof(bool), + typeof(HotkeyControl), + new PropertyMetadata(default(bool)) + ); - protected virtual void OnHotkeyChanged() => HotkeyChanged?.Invoke(this, EventArgs.Empty); - - public HotkeyControl() + public bool ValidateKeyGesture { - InitializeComponent(); + get { return (bool)GetValue(ValidateKeyGestureProperty); } + set { SetValue(ValidateKeyGestureProperty, value); } } - private CancellationTokenSource hotkeyUpdateSource; + public static readonly DependencyProperty DefaultHotkeyProperty = DependencyProperty.Register( + nameof(DefaultHotkey), + typeof(string), + typeof(HotkeyControl), + new PropertyMetadata(default(string)) + ); - private void TbHotkey_OnPreviewKeyDown(object sender, KeyEventArgs e) + public string DefaultHotkey { - hotkeyUpdateSource?.Cancel(); - hotkeyUpdateSource?.Dispose(); - hotkeyUpdateSource = new(); - var token = hotkeyUpdateSource.Token; - e.Handled = true; + get { return (string)GetValue(DefaultHotkeyProperty); } + set { SetValue(DefaultHotkeyProperty, value); } + } - //when alt is pressed, the real key should be e.SystemKey - Key key = e.Key == Key.System ? e.SystemKey : e.Key; - - SpecialKeyState specialKeyState = GlobalHotkey.CheckModifiers(); - - var hotkeyModel = new HotkeyModel( - specialKeyState.AltPressed, - specialKeyState.ShiftPressed, - specialKeyState.WinPressed, - specialKeyState.CtrlPressed, - key); - - if (hotkeyModel.Equals(CurrentHotkey)) + private static void OnHotkeyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not HotkeyControl hotkeyControl) { return; } - _ = Dispatcher.InvokeAsync(async () => - { - await Task.Delay(500, token); - if (!token.IsCancellationRequested) - await SetHotkeyAsync(hotkeyModel); - }); + hotkeyControl.SetKeysToDisplay(new HotkeyModel(hotkeyControl.Hotkey)); + hotkeyControl.CurrentHotkey = new HotkeyModel(hotkeyControl.Hotkey); } - public async Task SetHotkeyAsync(HotkeyModel keyModel, bool triggerValidate = true) - { - tbHotkey.Text = keyModel.ToString(); - tbHotkey.Select(tbHotkey.Text.Length, 0); + public static readonly DependencyProperty ChangeHotkeyProperty = DependencyProperty.Register( + nameof(ChangeHotkey), + typeof(ICommand), + typeof(HotkeyControl), + new PropertyMetadata(default(ICommand)) + ); + + public ICommand? ChangeHotkey + { + get { return (ICommand)GetValue(ChangeHotkeyProperty); } + set { SetValue(ChangeHotkeyProperty, value); } + } + + + public static readonly DependencyProperty HotkeyProperty = DependencyProperty.Register( + nameof(Hotkey), + typeof(string), + typeof(HotkeyControl), + new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnHotkeyChanged) + ); + + public string Hotkey + { + get { return (string)GetValue(HotkeyProperty); } + set { SetValue(HotkeyProperty, value); } + } + + public HotkeyControl() + { + InitializeComponent(); + + HotkeyList.ItemsSource = KeysToDisplay; + SetKeysToDisplay(CurrentHotkey); + } + + private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => + hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); + + public string EmptyHotkey => InternationalizationManager.Instance.GetTranslation("none"); + + public ObservableCollection KeysToDisplay { get; set; } = new(); + + public HotkeyModel CurrentHotkey { get; private set; } = new(false, false, false, false, Key.None); + + + public void GetNewHotkey(object sender, RoutedEventArgs e) + { + OpenHotkeyDialog(); + } + + private async Task OpenHotkeyDialog() + { + if (!string.IsNullOrEmpty(Hotkey)) + { + HotKeyMapper.RemoveHotkey(Hotkey); + } + + var dialog = new HotkeyControlDialog(Hotkey, DefaultHotkey, HotkeySettings, WindowTitle); + await dialog.ShowAsync(); + switch (dialog.ResultType) + { + case HotkeyControlDialog.EResultType.Cancel: + SetHotkey(Hotkey); + return; + case HotkeyControlDialog.EResultType.Save: + SetHotkey(dialog.ResultValue); + break; + case HotkeyControlDialog.EResultType.Delete: + Delete(); + break; + } + } + + + private void SetHotkey(HotkeyModel keyModel, bool triggerValidate = true) + { if (triggerValidate) { bool hotkeyAvailable = CheckHotkeyAvailability(keyModel, ValidateKeyGesture); - CurrentHotkeyAvailable = hotkeyAvailable; - SetMessage(hotkeyAvailable); - OnHotkeyChanged(); - var token = hotkeyUpdateSource.Token; - await Task.Delay(500, token); - if (token.IsCancellationRequested) - return; - - if (CurrentHotkeyAvailable) + if (!hotkeyAvailable) { - CurrentHotkey = keyModel; - // To trigger LostFocus - FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null); - Keyboard.ClearFocus(); + return; } + + Hotkey = keyModel.ToString(); + SetKeysToDisplay(CurrentHotkey); + ChangeHotkey?.Execute(keyModel); } else { - CurrentHotkey = keyModel; + Hotkey = keyModel.ToString(); + ChangeHotkey?.Execute(keyModel); } } - - public Task SetHotkeyAsync(string keyStr, bool triggerValidate = true) + + public void Delete() { - return SetHotkeyAsync(new HotkeyModel(keyStr), triggerValidate); + if (!string.IsNullOrEmpty(Hotkey)) + HotKeyMapper.RemoveHotkey(Hotkey); + Hotkey = ""; + SetKeysToDisplay(new HotkeyModel(false, false, false, false, Key.None)); } - private static bool CheckHotkeyAvailability(HotkeyModel hotkey, bool validateKeyGesture) => hotkey.Validate(validateKeyGesture) && HotKeyMapper.CheckAvailability(hotkey); - - public new bool IsFocused => tbHotkey.IsFocused; - - private void tbHotkey_LostFocus(object sender, RoutedEventArgs e) + private void SetKeysToDisplay(HotkeyModel? hotkey) { - tbHotkey.Text = CurrentHotkey?.ToString() ?? ""; - tbHotkey.Select(tbHotkey.Text.Length, 0); - } + KeysToDisplay.Clear(); - private void tbHotkey_GotFocus(object sender, RoutedEventArgs e) - { - ResetMessage(); - } - - private void ResetMessage() - { - tbMsg.Text = InternationalizationManager.Instance.GetTranslation("flowlauncherPressHotkey"); - tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B"); - } - - private void SetMessage(bool hotkeyAvailable) - { - if (!hotkeyAvailable) + if (hotkey == null || hotkey == default(HotkeyModel)) { - tbMsg.Foreground = new SolidColorBrush(Colors.Red); - tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable"); + KeysToDisplay.Add(EmptyHotkey); + return; } - else + + foreach (var key in hotkey.Value.EnumerateDisplayKeys()!) { - tbMsg.Foreground = new SolidColorBrush(Colors.Green); - tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success"); + KeysToDisplay.Add(key); } - tbMsg.Visibility = Visibility.Visible; + } + + public void SetHotkey(string? keyStr, bool triggerValidate = true) + { + SetHotkey(new HotkeyModel(keyStr), triggerValidate); } } } diff --git a/Flow.Launcher/HotkeyControlDialog.xaml b/Flow.Launcher/HotkeyControlDialog.xaml new file mode 100644 index 000000000..9a5872f4d --- /dev/null +++ b/Flow.Launcher/HotkeyControlDialog.xaml @@ -0,0 +1,169 @@ + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/Resources/Controls/HotkeyDisplay.xaml.cs b/Flow.Launcher/Resources/Controls/HotkeyDisplay.xaml.cs new file mode 100644 index 000000000..9b19ffd86 --- /dev/null +++ b/Flow.Launcher/Resources/Controls/HotkeyDisplay.xaml.cs @@ -0,0 +1,63 @@ +using System.Collections.ObjectModel; +using System.Windows; +using System.Windows.Controls; + +namespace Flow.Launcher.Resources.Controls +{ + public partial class HotkeyDisplay : UserControl + { + public enum DisplayType + { + Default, + Small + } + + public HotkeyDisplay() + { + InitializeComponent(); + //List stringList =e.NewValue.Split('+').ToList(); + Values = new ObservableCollection(); + KeysControl.ItemsSource = Values; + } + + public string Keys + { + get { return (string)GetValue(KeysProperty); } + set { SetValue(KeysProperty, value); } + } + + public static readonly DependencyProperty KeysProperty = + DependencyProperty.Register(nameof(Keys), typeof(string), typeof(HotkeyDisplay), + new PropertyMetadata(string.Empty, keyChanged)); + + public DisplayType Type + { + get { return (DisplayType)GetValue(TypeProperty); } + set { SetValue(TypeProperty, value); } + } + + public static readonly DependencyProperty TypeProperty = + DependencyProperty.Register(nameof(Type), typeof(DisplayType), typeof(HotkeyDisplay), + new PropertyMetadata(DisplayType.Default)); + + private static void keyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var control = d as UserControl; + if (null == control) return; // This should not be possible + + var newValue = e.NewValue as string; + if (null == newValue) return; + + if (d is not HotkeyDisplay hotkeyDisplay) + return; + + hotkeyDisplay.Values.Clear(); + foreach (var key in newValue.Split('+')) + { + hotkeyDisplay.Values.Add(key); + } + } + + public ObservableCollection Values { get; set; } + } +} diff --git a/Flow.Launcher/Resources/Controls/HyperLink.xaml b/Flow.Launcher/Resources/Controls/HyperLink.xaml new file mode 100644 index 000000000..9ea550afd --- /dev/null +++ b/Flow.Launcher/Resources/Controls/HyperLink.xaml @@ -0,0 +1,14 @@ + + + + + + + diff --git a/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs b/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs new file mode 100644 index 000000000..855cccdbd --- /dev/null +++ b/Flow.Launcher/Resources/Controls/HyperLink.xaml.cs @@ -0,0 +1,39 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Navigation; + +namespace Flow.Launcher.Resources.Controls; + +public partial class HyperLink : UserControl +{ + public static readonly DependencyProperty UriProperty = DependencyProperty.Register( + nameof(Uri), typeof(string), typeof(HyperLink), new PropertyMetadata(default(string)) + ); + + public string Uri + { + get => (string)GetValue(UriProperty); + set => SetValue(UriProperty, value); + } + + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + nameof(Text), typeof(string), typeof(HyperLink), new PropertyMetadata(default(string)) + ); + + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + public HyperLink() + { + InitializeComponent(); + } + + private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + App.API.OpenUrl(e.Uri); + e.Handled = true; + } +} diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml new file mode 100644 index 000000000..ed3c29690 --- /dev/null +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml.cs b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml.cs new file mode 100644 index 000000000..dfa03a204 --- /dev/null +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplay.xaml.cs @@ -0,0 +1,9 @@ +namespace Flow.Launcher.Resources.Controls; + +public partial class InstalledPluginDisplay +{ + public InstalledPluginDisplay() + { + InitializeComponent(); + } +} diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml new file mode 100644 index 000000000..83a771728 --- /dev/null +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml.cs b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml.cs new file mode 100644 index 000000000..f8d0afe61 --- /dev/null +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayBottomData.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows.Controls; + +namespace Flow.Launcher.Resources.Controls; + +public partial class InstalledPluginDisplayBottomData : UserControl +{ + public InstalledPluginDisplayBottomData() + { + InitializeComponent(); + } +} diff --git a/Flow.Launcher/Resources/Controls/InstalledPluginDisplayKeyword.xaml b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayKeyword.xaml new file mode 100644 index 000000000..ff2f14c4b --- /dev/null +++ b/Flow.Launcher/Resources/Controls/InstalledPluginDisplayKeyword.xaml @@ -0,0 +1,47 @@ + + + + +  + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs new file mode 100644 index 000000000..de6a4df5e --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneAbout.xaml.cs @@ -0,0 +1,29 @@ +using System; +using System.Windows.Navigation; +using Flow.Launcher.SettingPages.ViewModels; + +namespace Flow.Launcher.SettingPages.Views; + +public partial class SettingsPaneAbout +{ + private SettingsPaneAboutViewModel _viewModel = null!; + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (!IsInitialized) + { + if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings, Updater: { } updater }) + throw new ArgumentException("Settings are required for SettingsPaneAbout."); + _viewModel = new SettingsPaneAboutViewModel(settings, updater); + DataContext = _viewModel; + InitializeComponent(); + } + base.OnNavigatedTo(e); + } + + private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + App.API.OpenUrl(e.Uri.AbsoluteUri); + e.Handled = true; + } +} diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml new file mode 100644 index 000000000..30e065b16 --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneGeneral.xaml @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs new file mode 100644 index 000000000..dfb4a7eaf --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePluginStore.xaml.cs @@ -0,0 +1,66 @@ +using System; +using System.ComponentModel; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Navigation; +using Flow.Launcher.Core.Plugin; +using Flow.Launcher.SettingPages.ViewModels; +using Flow.Launcher.ViewModel; + +namespace Flow.Launcher.SettingPages.Views; + +public partial class SettingsPanePluginStore +{ + private SettingsPanePluginStoreViewModel _viewModel = null!; + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (!IsInitialized) + { + if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings }) + throw new ArgumentException($"Settings are required for {nameof(SettingsPanePluginStore)}."); + _viewModel = new SettingsPanePluginStoreViewModel(); + DataContext = _viewModel; + InitializeComponent(); + } + _viewModel.PropertyChanged += ViewModel_PropertyChanged; + base.OnNavigatedTo(e); + } + + private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(SettingsPanePluginStoreViewModel.FilterText)) + { + ((CollectionViewSource)FindResource("PluginStoreCollectionView")).View.Refresh(); + } + } + + protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) + { + _viewModel.PropertyChanged -= ViewModel_PropertyChanged; + base.OnNavigatingFrom(e); + } + + private void SettingsPanePlugins_OnKeyDown(object sender, KeyEventArgs e) + { + if (e.Key is not Key.F || Keyboard.Modifiers is not ModifierKeys.Control) return; + PluginStoreFilterTextbox.Focus(); + } + + private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e) + { + PluginManager.API.OpenUrl(e.Uri.AbsoluteUri); + e.Handled = true; + } + + private void PluginStoreCollectionView_OnFilter(object sender, FilterEventArgs e) + { + if (e.Item is not PluginStoreItemViewModel plugin) + { + e.Accepted = false; + return; + } + + e.Accepted = _viewModel.SatisfiesFilter(plugin); + } +} diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml new file mode 100644 index 000000000..37079a46f --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs new file mode 100644 index 000000000..d48505c3d --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPanePlugins.xaml.cs @@ -0,0 +1,30 @@ +using System; +using System.Windows.Input; +using System.Windows.Navigation; +using Flow.Launcher.SettingPages.ViewModels; + +namespace Flow.Launcher.SettingPages.Views; + +public partial class SettingsPanePlugins +{ + private SettingsPanePluginsViewModel _viewModel = null!; + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + if (!IsInitialized) + { + if (e.ExtraData is not SettingWindow.PaneData { Settings: { } settings }) + throw new ArgumentException("Settings are required for SettingsPaneHotkey."); + _viewModel = new SettingsPanePluginsViewModel(settings); + DataContext = _viewModel; + InitializeComponent(); + } + base.OnNavigatedTo(e); + } + + private void SettingsPanePlugins_OnKeyDown(object sender, KeyEventArgs e) + { + if (e.Key is not Key.F || Keyboard.Modifiers is not ModifierKeys.Control) return; + PluginFilterTextbox.Focus(); + } +} diff --git a/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml new file mode 100644 index 000000000..768abbf97 --- /dev/null +++ b/Flow.Launcher/SettingPages/Views/SettingsPaneProxy.xaml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + @@ -590,2739 +196,72 @@ Grid.Row="0" Width="50" Height="50" + RenderOptions.BitmapScalingMode="HighQuality" Source="images/app.png" /> + TextAlignment="Center" /> - - - - - - - - - - - -  - - - - + + - - - + + + + + - - - - - - - -  - - - + + + + + - - - - - - - -  - - - + + + + + - - - - - - - - + + + + + + + + + + - - - - - - - - - + + + + + - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - -  - - - - - - - - - - - - -  - - - - - - - - - - - -  - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - -  - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - -  - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - - -  - - - - - - - - - - - - + diff --git a/Flow.Launcher/SettingWindow.xaml.cs b/Flow.Launcher/SettingWindow.xaml.cs index 7301be130..de4fd1f91 100644 --- a/Flow.Launcher/SettingWindow.xaml.cs +++ b/Flow.Launcher/SettingWindow.xaml.cs @@ -1,538 +1,172 @@ -using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Core.Resource; -using Flow.Launcher.Helper; -using Flow.Launcher.Infrastructure; -using Flow.Launcher.Infrastructure.Hotkey; -using Flow.Launcher.Infrastructure.UserSettings; -using Flow.Launcher.Plugin; -using Flow.Launcher.ViewModel; -using ModernWpf; -using ModernWpf.Controls; -using System; -using System.ComponentModel; -using System.IO; +using System; using System.Windows; -using System.Windows.Data; using System.Windows.Forms; using System.Windows.Input; using System.Windows.Interop; -using System.Windows.Navigation; -using Button = System.Windows.Controls.Button; -using Control = System.Windows.Controls.Control; -using KeyEventArgs = System.Windows.Input.KeyEventArgs; -using MessageBox = System.Windows.MessageBox; +using Flow.Launcher.Core; +using Flow.Launcher.Core.Configuration; +using Flow.Launcher.Helper; +using Flow.Launcher.Infrastructure.UserSettings; +using Flow.Launcher.Plugin; +using Flow.Launcher.SettingPages.Views; +using Flow.Launcher.ViewModel; +using ModernWpf.Controls; using TextBox = System.Windows.Controls.TextBox; -using ThemeManager = ModernWpf.ThemeManager; -namespace Flow.Launcher +namespace Flow.Launcher; + +public partial class SettingWindow { - public partial class SettingWindow + private readonly IPublicAPI _api; + private readonly Settings _settings; + private readonly SettingWindowViewModel _viewModel; + + public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel) { - public readonly IPublicAPI API; - private Settings settings; - private SettingWindowViewModel viewModel; + _settings = viewModel.Settings; + DataContext = viewModel; + _viewModel = viewModel; + _api = api; + InitializePosition(); + InitializeComponent(); + NavView.SelectedItem = NavView.MenuItems[0]; /* Set First Page */ + } - public SettingWindow(IPublicAPI api, SettingWindowViewModel viewModel) + private void OnLoaded(object sender, RoutedEventArgs e) + { + RefreshMaximizeRestoreButton(); + // Fix (workaround) for the window freezes after lock screen (Win+L) + // https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf + HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource; + HwndTarget hwndTarget = hwndSource.CompositionTarget; + hwndTarget.RenderMode = RenderMode.Default; + + InitializePosition(); + } + + private void OnClosed(object sender, EventArgs e) + { + _settings.SettingWindowState = WindowState; + _settings.SettingWindowTop = Top; + _settings.SettingWindowLeft = Left; + _viewModel.Save(); + _api.SavePluginSettings(); + } + + private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e) + { + Close(); + } + + private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */ + { + if (Keyboard.FocusedElement is not TextBox textBox) { - settings = viewModel.Settings; - DataContext = viewModel; - this.viewModel = viewModel; - API = api; - InitializePosition(); - InitializeComponent(); - + return; } + var tRequest = new TraversalRequest(FocusNavigationDirection.Next); + textBox.MoveFocus(tRequest); + } - #region General + /* Custom TitleBar */ - private void OnLoaded(object sender, RoutedEventArgs e) + private void OnMinimizeButtonClick(object sender, RoutedEventArgs e) + { + WindowState = WindowState.Minimized; + } + + private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e) + { + WindowState = WindowState switch { - RefreshMaximizeRestoreButton(); - // Fix (workaround) for the window freezes after lock screen (Win+L) - // https://stackoverflow.com/questions/4951058/software-rendering-mode-wpf - HwndSource hwndSource = PresentationSource.FromVisual(this) as HwndSource; - HwndTarget hwndTarget = hwndSource.CompositionTarget; - hwndTarget.RenderMode = RenderMode.SoftwareOnly; + WindowState.Maximized => WindowState.Normal, + _ => WindowState.Maximized + }; + } - pluginListView = (CollectionView)CollectionViewSource.GetDefaultView(Plugins.ItemsSource); - pluginListView.Filter = PluginListFilter; + private void OnCloseButtonClick(object sender, RoutedEventArgs e) + { + Close(); + } - pluginStoreView = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource); - pluginStoreView.Filter = PluginStoreFilter; - - viewModel.PropertyChanged += new PropertyChangedEventHandler(SettingsWindowViewModelChanged); - - InitializePosition(); - } - - private void SettingsWindowViewModelChanged(object sender, PropertyChangedEventArgs e) + private void RefreshMaximizeRestoreButton() + { + if (WindowState == WindowState.Maximized) { - if (e.PropertyName == nameof(viewModel.ExternalPlugins)) - { - pluginStoreView = (CollectionView)CollectionViewSource.GetDefaultView(StoreListBox.ItemsSource); - pluginStoreView.Filter = PluginStoreFilter; - pluginStoreView.Refresh(); - } + MaximizeButton.Visibility = Visibility.Collapsed; + RestoreButton.Visibility = Visibility.Visible; } - - private void OnSelectPythonPathClick(object sender, RoutedEventArgs e) + else { - var selectedFile = viewModel.GetFileFromDialog( - InternationalizationManager.Instance.GetTranslation("selectPythonExecutable"), - "Python|pythonw.exe"); - - if (!string.IsNullOrEmpty(selectedFile)) - settings.PluginSettings.PythonExecutablePath = selectedFile; - } - - private void OnSelectNodePathClick(object sender, RoutedEventArgs e) - { - var selectedFile = viewModel.GetFileFromDialog( - InternationalizationManager.Instance.GetTranslation("selectNodeExecutable")); - - if (!string.IsNullOrEmpty(selectedFile)) - settings.PluginSettings.NodeExecutablePath = selectedFile; - } - - private void OnSelectFileManagerClick(object sender, RoutedEventArgs e) - { - SelectFileManagerWindow fileManagerChangeWindow = new SelectFileManagerWindow(settings); - fileManagerChangeWindow.ShowDialog(); - } - - private void OnSelectDefaultBrowserClick(object sender, RoutedEventArgs e) - { - var browserWindow = new SelectBrowserWindow(settings); - browserWindow.ShowDialog(); - } - - #endregion - - #region Hotkey - - private void OnHotkeyControlLoaded(object sender, RoutedEventArgs e) - { - _ = HotkeyControl.SetHotkeyAsync(viewModel.Settings.Hotkey, false); - } - - private void OnHotkeyControlFocused(object sender, RoutedEventArgs e) - { - HotKeyMapper.RemoveHotkey(settings.Hotkey); - } - - private void OnHotkeyControlFocusLost(object sender, RoutedEventArgs e) - { - if (HotkeyControl.CurrentHotkeyAvailable) - { - HotKeyMapper.SetHotkey(HotkeyControl.CurrentHotkey, HotKeyMapper.OnToggleHotkey); - HotKeyMapper.RemoveHotkey(settings.Hotkey); - settings.Hotkey = HotkeyControl.CurrentHotkey.ToString(); - } - else - { - HotKeyMapper.SetHotkey(new HotkeyModel(settings.Hotkey), HotKeyMapper.OnToggleHotkey); - } - } - - private void OnPreviewHotkeyControlLoaded(object sender, RoutedEventArgs e) - { - _ = PreviewHotkeyControl.SetHotkeyAsync(settings.PreviewHotkey, false); - } - - private void OnPreviewHotkeyControlFocusLost(object sender, RoutedEventArgs e) - { - if (PreviewHotkeyControl.CurrentHotkeyAvailable) - { - settings.PreviewHotkey = PreviewHotkeyControl.CurrentHotkey.ToString(); - } - } - - private void OnDeleteCustomHotkeyClick(object sender, RoutedEventArgs e) - { - var item = viewModel.SelectedCustomPluginHotkey; - if (item == null) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); - return; - } - - string deleteWarning = - string.Format(InternationalizationManager.Instance.GetTranslation("deleteCustomHotkeyWarning"), - item.Hotkey); - if ( - MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - settings.CustomPluginHotkeys.Remove(item); - HotKeyMapper.RemoveHotkey(item.Hotkey); - } - } - - private void OnEditCustomHotkeyClick(object sender, RoutedEventArgs e) - { - var item = viewModel.SelectedCustomPluginHotkey; - if (item != null) - { - CustomQueryHotkeySetting window = new CustomQueryHotkeySetting(this, settings); - window.UpdateItem(item); - window.ShowDialog(); - } - else - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); - } - } - - private void OnAddCustomHotkeyClick(object sender, RoutedEventArgs e) - { - new CustomQueryHotkeySetting(this, settings).ShowDialog(); - } - - #endregion - - #region Plugin - - private void OnPluginToggled(object sender, RoutedEventArgs e) - { - var id = viewModel.SelectedPlugin.PluginPair.Metadata.ID; - // used to sync the current status from the plugin manager into the setting to keep consistency after save - settings.PluginSettings.Plugins[id].Disabled = viewModel.SelectedPlugin.PluginPair.Metadata.Disabled; - } - - private void OnPluginPriorityClick(object sender, RoutedEventArgs e) - { - if (sender is Control { DataContext: PluginViewModel pluginViewModel }) - { - PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(pluginViewModel.PluginPair.Metadata.ID, pluginViewModel); - priorityChangeWindow.ShowDialog(); - } - } - - #endregion - - #region Proxy - - private void OnTestProxyClick(object sender, RoutedEventArgs e) - { // TODO: change to command - var msg = viewModel.TestProxy(); - MessageBox.Show(msg); // TODO: add message box service - } - - #endregion - - private void OnCheckUpdates(object sender, RoutedEventArgs e) - { - viewModel.UpdateApp(); // TODO: change to command - } - - private void OnRequestNavigate(object sender, RequestNavigateEventArgs e) - { - API.OpenUrl(e.Uri.AbsoluteUri); - e.Handled = true; - } - - private void OnClosed(object sender, EventArgs e) - { - settings.SettingWindowState = WindowState; - settings.SettingWindowTop = Top; - settings.SettingWindowLeft = Left; - viewModel.Save(); - API.SavePluginSettings(); - } - - private void OnCloseExecuted(object sender, ExecutedRoutedEventArgs e) - { - Close(); - } - - private void OpenThemeFolder(object sender, RoutedEventArgs e) - { - PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Themes)); - } - - private void OpenSettingFolder(object sender, RoutedEventArgs e) - { - PluginManager.API.OpenDirectory(Path.Combine(DataLocation.DataDirectory(), Constant.Settings)); - } - - private void OpenWelcomeWindow(object sender, RoutedEventArgs e) - { - var WelcomeWindow = new WelcomeWindow(settings); - WelcomeWindow.ShowDialog(); - } - private void OpenLogFolder(object sender, RoutedEventArgs e) - { - viewModel.OpenLogFolder(); - } - private void ClearLogFolder(object sender, RoutedEventArgs e) - { - var confirmResult = MessageBox.Show( - InternationalizationManager.Instance.GetTranslation("clearlogfolderMessage"), - InternationalizationManager.Instance.GetTranslation("clearlogfolder"), - MessageBoxButton.YesNo); - - if (confirmResult == MessageBoxResult.Yes) - { - viewModel.ClearLogFolder(); - } - } - - private void OnExternalPluginInstallClick(object sender, RoutedEventArgs e) - { - if (sender is not Button { DataContext: PluginStoreItemViewModel plugin } button) - { - return; - } - - if (storeClickedButton != null) - { - FlyoutService.GetFlyout(storeClickedButton).Hide(); - } - - viewModel.DisplayPluginQuery($"install {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); - } - - private void OnExternalPluginUninstallClick(object sender, MouseButtonEventArgs e) - { - if (e.ChangedButton == MouseButton.Left) - { - var name = viewModel.SelectedPlugin.PluginPair.Metadata.Name; - viewModel.DisplayPluginQuery($"uninstall {name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); - } - } - - private void OnExternalPluginUninstallClick(object sender, RoutedEventArgs e) - { - if (storeClickedButton != null) - { - FlyoutService.GetFlyout(storeClickedButton).Hide(); - } - - if (sender is Button { DataContext: PluginStoreItemViewModel plugin }) - viewModel.DisplayPluginQuery($"uninstall {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); - - } - - private void OnExternalPluginUpdateClick(object sender, RoutedEventArgs e) - { - if (storeClickedButton != null) - { - FlyoutService.GetFlyout(storeClickedButton).Hide(); - } - if (sender is Button { DataContext: PluginStoreItemViewModel plugin }) - viewModel.DisplayPluginQuery($"update {plugin.Name}", PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7")); - - } - - private void window_MouseDown(object sender, MouseButtonEventArgs e) /* for close hotkey popup */ - { - if (Keyboard.FocusedElement is not TextBox textBox) - { - return; - } - var tRequest = new TraversalRequest(FocusNavigationDirection.Next); - textBox.MoveFocus(tRequest); - } - - private void ColorSchemeSelectedIndexChanged(object sender, EventArgs e) - => ThemeManager.Current.ApplicationTheme = settings.ColorScheme switch - { - Constant.Light => ApplicationTheme.Light, - Constant.Dark => ApplicationTheme.Dark, - Constant.System => null, - _ => ThemeManager.Current.ApplicationTheme - }; - - /* Custom TitleBar */ - - private void OnMinimizeButtonClick(object sender, RoutedEventArgs e) - { - WindowState = WindowState.Minimized; - } - - private void OnMaximizeRestoreButtonClick(object sender, RoutedEventArgs e) - { - WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; - } - - private void OnCloseButtonClick(object sender, RoutedEventArgs e) - { - - Close(); - } - - private void RefreshMaximizeRestoreButton() - { - if (WindowState == WindowState.Maximized) - { - maximizeButton.Visibility = Visibility.Collapsed; - restoreButton.Visibility = Visibility.Visible; - } - else - { - maximizeButton.Visibility = Visibility.Visible; - restoreButton.Visibility = Visibility.Collapsed; - } - } - - private void Window_StateChanged(object sender, EventArgs e) - { - RefreshMaximizeRestoreButton(); - } - - #region Shortcut - - private void OnDeleteCustomShortCutClick(object sender, RoutedEventArgs e) - { - viewModel.DeleteSelectedCustomShortcut(); - } - - private void OnEditCustomShortCutClick(object sender, RoutedEventArgs e) - { - if (viewModel.EditSelectedCustomShortcut()) - { - customShortcutView.Items.Refresh(); - } - } - - private void OnAddCustomShortCutClick(object sender, RoutedEventArgs e) - { - viewModel.AddCustomShortcut(); - } - - #endregion - - private CollectionView pluginListView; - private CollectionView pluginStoreView; - - private bool PluginListFilter(object item) - { - if (string.IsNullOrEmpty(pluginFilterTxb.Text)) - return true; - if (item is PluginViewModel model) - { - return StringMatcher.FuzzySearch(pluginFilterTxb.Text, model.PluginPair.Metadata.Name).IsSearchPrecisionScoreMet(); - } - return false; - } - - private bool PluginStoreFilter(object item) - { - if (string.IsNullOrEmpty(pluginStoreFilterTxb.Text)) - return true; - if (item is PluginStoreItemViewModel model) - { - return StringMatcher.FuzzySearch(pluginStoreFilterTxb.Text, model.Name).IsSearchPrecisionScoreMet() - || StringMatcher.FuzzySearch(pluginStoreFilterTxb.Text, model.Description).IsSearchPrecisionScoreMet(); - } - return false; - } - - private string lastPluginListSearch = ""; - private string lastPluginStoreSearch = ""; - - private void RefreshPluginListEventHandler(object sender, RoutedEventArgs e) - { - if (pluginFilterTxb.Text != lastPluginListSearch) - { - lastPluginListSearch = pluginFilterTxb.Text; - pluginListView.Refresh(); - } - } - - private void RefreshPluginStoreEventHandler(object sender, RoutedEventArgs e) - { - if (pluginStoreFilterTxb.Text != lastPluginStoreSearch) - { - lastPluginStoreSearch = pluginStoreFilterTxb.Text; - pluginStoreView.Refresh(); - } - } - - private void PluginFilterTxb_OnKeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.Enter) - RefreshPluginListEventHandler(sender, e); - } - - private void PluginStoreFilterTxb_OnKeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.Enter) - RefreshPluginStoreEventHandler(sender, e); - } - - private void OnPluginSettingKeydown(object sender, KeyEventArgs e) - { - if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control && e.Key == Key.F) - pluginFilterTxb.Focus(); - } - - private void PluginStore_OnKeyDown(object sender, KeyEventArgs e) - { - if (e.Key == Key.F && (Keyboard.Modifiers & ModifierKeys.Control) != 0) - { - pluginStoreFilterTxb.Focus(); - } - } - - public void InitializePosition() - { - if (settings.SettingWindowTop >= 0 && settings.SettingWindowLeft >= 0) - { - Top = settings.SettingWindowTop; - Left = settings.SettingWindowLeft; - } - else - { - Top = WindowTop(); - Left = WindowLeft(); - } - WindowState = settings.SettingWindowState; - } - - public double WindowLeft() - { - var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); - var left = (dip2.X - this.ActualWidth) / 2 + dip1.X; - return left; - } - - public double WindowTop() - { - var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); - var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); - var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20; - return top; - } - - private Button storeClickedButton; - - private void StoreListItem_Click(object sender, RoutedEventArgs e) - { - if (sender is not Button button) - return; - - storeClickedButton = button; - - var flyout = FlyoutService.GetFlyout(button); - flyout.Closed += (_, _) => - { - storeClickedButton = null; - }; - - } - - private void PluginStore_GotFocus(object sender, RoutedEventArgs e) - { - Keyboard.Focus(pluginStoreFilterTxb); - } - - private void Plugin_GotFocus(object sender, RoutedEventArgs e) - { - Keyboard.Focus(pluginFilterTxb); + MaximizeButton.Visibility = Visibility.Visible; + RestoreButton.Visibility = Visibility.Collapsed; } } + + private void Window_StateChanged(object sender, EventArgs e) + { + RefreshMaximizeRestoreButton(); + } + + public void InitializePosition() + { + if (_settings.SettingWindowTop == null || _settings.SettingWindowLeft == null) + { + Top = WindowTop(); + Left = WindowLeft(); + } + else + { + Top = _settings.SettingWindowTop.Value; + Left = _settings.SettingWindowLeft.Value; + } + WindowState = _settings.SettingWindowState; + } + + private double WindowLeft() + { + var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); + var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.X, 0); + var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, screen.WorkingArea.Width, 0); + var left = (dip2.X - this.ActualWidth) / 2 + dip1.X; + return left; + } + + private double WindowTop() + { + var screen = Screen.FromPoint(System.Windows.Forms.Cursor.Position); + var dip1 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Y); + var dip2 = WindowsInteropHelper.TransformPixelsToDIP(this, 0, screen.WorkingArea.Height); + var top = (dip2.Y - this.ActualHeight) / 2 + dip1.Y - 20; + return top; + } + + private void NavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) + { + var paneData = new PaneData(_settings, _viewModel.Updater, _viewModel.Portable); + if (args.IsSettingsSelected) + { + ContentFrame.Navigate(typeof(SettingsPaneGeneral), paneData); + } + else + { + var selectedItem = (NavigationViewItem)args.SelectedItem; + if (selectedItem == null) return; + + var pageType = selectedItem.Name switch + { + nameof(General) => typeof(SettingsPaneGeneral), + nameof(Plugins) => typeof(SettingsPanePlugins), + nameof(PluginStore) => typeof(SettingsPanePluginStore), + nameof(Theme) => typeof(SettingsPaneTheme), + nameof(Hotkey) => typeof(SettingsPaneHotkey), + nameof(Proxy) => typeof(SettingsPaneProxy), + nameof(About) => typeof(SettingsPaneAbout), + _ => typeof(SettingsPaneGeneral) + }; + ContentFrame.Navigate(pageType, paneData); + } + } + + public record PaneData(Settings Settings, Updater Updater, IPortable Portable); } diff --git a/Flow.Launcher/Themes/Base.xaml b/Flow.Launcher/Themes/Base.xaml index 2ba3a5929..b96cb661e 100644 --- a/Flow.Launcher/Themes/Base.xaml +++ b/Flow.Launcher/Themes/Base.xaml @@ -28,8 +28,8 @@ - - + + @@ -69,7 +69,7 @@ @@ -115,18 +117,20 @@ - + @@ -135,10 +139,11 @@ - + + @@ -188,7 +193,7 @@ @@ -312,7 +317,7 @@ x:Name="PART_VerticalScrollBar" Grid.Row="0" Grid.Column="0" - Margin="0,0,0,0" + Margin="0 0 0 0" HorizontalAlignment="Right" AutomationProperties.AutomationId="VerticalScrollBar" Cursor="Arrow" @@ -368,22 +373,7 @@ - + @@ -413,16 +403,30 @@ + @@ -495,7 +500,7 @@ @@ -503,7 +508,7 @@ x:Key="ClockPanelPosition" BasedOn="{StaticResource BaseClockPanelPosition}" TargetType="{x:Type Canvas}"> - + - \ No newline at end of file + diff --git a/Flow.Launcher/Themes/BlurWhite.xaml b/Flow.Launcher/Themes/BlurWhite.xaml index 4406724b8..a13f3bfcc 100644 --- a/Flow.Launcher/Themes/BlurWhite.xaml +++ b/Flow.Launcher/Themes/BlurWhite.xaml @@ -1,4 +1,9 @@ - + @@ -96,7 +101,7 @@ TargetType="{x:Type Rectangle}"> - + - - - - - - - - - - - - - #f1f1f1 - - - - - - - - - - 5 - 10 0 10 0 - 0 0 0 10 - - - - - - - - \ No newline at end of file diff --git a/Flow.Launcher/Themes/Circle Light.xaml b/Flow.Launcher/Themes/Circle Light.xaml deleted file mode 100644 index e52e3a957..000000000 --- a/Flow.Launcher/Themes/Circle Light.xaml +++ /dev/null @@ -1,190 +0,0 @@ - - - - - - - - - - - - - - - - - #5046e5 - - - - - - - - - - 8 - 10 0 10 0 - 0 0 0 10 - - - - - - - - diff --git a/Flow.Launcher/Themes/Circle System.xaml b/Flow.Launcher/Themes/Circle System.xaml index 2b2ce7ca3..343e7c5bc 100644 --- a/Flow.Launcher/Themes/Circle System.xaml +++ b/Flow.Launcher/Themes/Circle System.xaml @@ -1,3 +1,8 @@ + - + @@ -27,7 +32,7 @@ x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}"> - + @@ -72,7 +77,7 @@ TargetType="{x:Type Rectangle}"> - + @@ -150,7 +155,7 @@ x:Key="ClockPanel" BasedOn="{StaticResource ClockPanel}" TargetType="{x:Type StackPanel}"> - + - \ No newline at end of file + diff --git a/Flow.Launcher/Themes/Cyan Dark.xaml b/Flow.Launcher/Themes/Cyan Dark.xaml index 60bc09002..106b1b6d9 100644 --- a/Flow.Launcher/Themes/Cyan Dark.xaml +++ b/Flow.Launcher/Themes/Cyan Dark.xaml @@ -27,7 +27,7 @@ x:Key="ItemGlyph" BasedOn="{StaticResource BaseGlyphStyle}" TargetType="{x:Type TextBlock}"> - + - + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Discord Dark.xaml b/Flow.Launcher/Themes/Discord Dark.xaml index 5315c7644..9e39ee5bd 100644 --- a/Flow.Launcher/Themes/Discord Dark.xaml +++ b/Flow.Launcher/Themes/Discord Dark.xaml @@ -1,4 +1,4 @@ - @@ -189,4 +189,4 @@ TargetType="{x:Type TextBlock}"> - + \ No newline at end of file diff --git a/Flow.Launcher/Themes/Dracula.xaml b/Flow.Launcher/Themes/Dracula.xaml index d150e7355..eb8cc9557 100644 --- a/Flow.Launcher/Themes/Dracula.xaml +++ b/Flow.Launcher/Themes/Dracula.xaml @@ -28,7 +28,6 @@ x:Key="QuerySuggestionBoxStyle" BasedOn="{StaticResource BaseQuerySuggestionBoxStyle}" TargetType="{x:Type TextBox}"> - @@ -98,7 +97,7 @@ diff --git a/Flow.Launcher/Themes/Pink.xaml b/Flow.Launcher/Themes/Pink.xaml index dc97e4320..d6f9813d0 100644 --- a/Flow.Launcher/Themes/Pink.xaml +++ b/Flow.Launcher/Themes/Pink.xaml @@ -2,45 +2,46 @@ - 0 0 0 4 + 0 0 0 0 - #cc1081 + #0e172c \ No newline at end of file diff --git a/Flow.Launcher/Themes/SlimLight.xaml b/Flow.Launcher/Themes/SlimLight.xaml index dc08eec30..078b07048 100644 --- a/Flow.Launcher/Themes/SlimLight.xaml +++ b/Flow.Launcher/Themes/SlimLight.xaml @@ -179,15 +179,28 @@ BasedOn="{StaticResource BaseClockBox}" TargetType="{x:Type TextBlock}"> - + + + + + + + + - - - - - - - - - - - - - - - #ccd0d4 - - - - - - - - - - - - - - - diff --git a/Flow.Launcher/Themes/Win11System.xaml b/Flow.Launcher/Themes/Win10System.xaml similarity index 89% rename from Flow.Launcher/Themes/Win11System.xaml rename to Flow.Launcher/Themes/Win10System.xaml index 3025f9a07..4f8ea5532 100644 --- a/Flow.Launcher/Themes/Win11System.xaml +++ b/Flow.Launcher/Themes/Win10System.xaml @@ -1,3 +1,8 @@ + - + - - + + diff --git a/Flow.Launcher/Themes/Win11Dark.xaml b/Flow.Launcher/Themes/Win11Dark.xaml deleted file mode 100644 index 5abb96cce..000000000 --- a/Flow.Launcher/Themes/Win11Dark.xaml +++ /dev/null @@ -1,195 +0,0 @@ - - - - - 0 0 0 8 - - - - - - - - - - - - - - - #198F8F8F - - - - - - - - - - - - - - - diff --git a/Flow.Launcher/Themes/Win11Light.xaml b/Flow.Launcher/Themes/Win11Light.xaml index e6f376f8b..b81c1cffc 100644 --- a/Flow.Launcher/Themes/Win11Light.xaml +++ b/Flow.Launcher/Themes/Win11Light.xaml @@ -1,72 +1,77 @@ + + - 0 0 0 8 + + - + + - + TargetType="{x:Type Window}" /> + - #198F8F8F - - - - + + + + + + + + 5 + 10 0 5 0 + 0 10 0 10 + diff --git a/Flow.Launcher/ViewModel/MainViewModel.cs b/Flow.Launcher/ViewModel/MainViewModel.cs index 9b799e582..6c17e21f0 100644 --- a/Flow.Launcher/ViewModel/MainViewModel.cs +++ b/Flow.Launcher/ViewModel/MainViewModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -19,8 +19,6 @@ using Microsoft.VisualStudio.Threading; using System.Text; using System.Threading.Channels; using ISavable = Flow.Launcher.Plugin.ISavable; -using System.IO; -using System.Collections.Specialized; using CommunityToolkit.Mvvm.Input; using System.Globalization; using System.Windows.Input; @@ -42,6 +40,7 @@ namespace Flow.Launcher.ViewModel private readonly FlowLauncherJsonStorage _userSelectedRecordStorage; private readonly FlowLauncherJsonStorage _topMostRecordStorage; private readonly History _history; + private int lastHistoryIndex = 1; private readonly UserSelectedRecord _userSelectedRecord; private readonly TopMostRecord _topMostRecord; @@ -71,6 +70,21 @@ namespace Flow.Launcher.ViewModel case nameof(Settings.WindowSize): OnPropertyChanged(nameof(MainWindowWidth)); break; + case nameof(Settings.WindowHeightSize): + OnPropertyChanged(nameof(MainWindowHeight)); + break; + case nameof(Settings.QueryBoxFontSize): + OnPropertyChanged(nameof(QueryBoxFontSize)); + break; + case nameof(Settings.ItemHeightSize): + OnPropertyChanged(nameof(ItemHeightSize)); + break; + case nameof(Settings.ResultItemFontSize): + OnPropertyChanged(nameof(ResultItemFontSize)); + break; + case nameof(Settings.ResultSubItemFontSize): + OnPropertyChanged(nameof(ResultSubItemFontSize)); + break; case nameof(Settings.AlwaysStartEn): OnPropertyChanged(nameof(StartWithEnglishMode)); break; @@ -80,6 +94,42 @@ namespace Flow.Launcher.ViewModel case nameof(Settings.PreviewHotkey): OnPropertyChanged(nameof(PreviewHotkey)); break; + case nameof(Settings.AutoCompleteHotkey): + OnPropertyChanged(nameof(AutoCompleteHotkey)); + break; + case nameof(Settings.CycleHistoryUpHotkey): + OnPropertyChanged(nameof(CycleHistoryUpHotkey)); + break; + case nameof(Settings.CycleHistoryDownHotkey): + OnPropertyChanged(nameof(CycleHistoryDownHotkey)); + break; + case nameof(Settings.AutoCompleteHotkey2): + OnPropertyChanged(nameof(AutoCompleteHotkey2)); + break; + case nameof(Settings.SelectNextItemHotkey): + OnPropertyChanged(nameof(SelectNextItemHotkey)); + break; + case nameof(Settings.SelectNextItemHotkey2): + OnPropertyChanged(nameof(SelectNextItemHotkey2)); + break; + case nameof(Settings.SelectPrevItemHotkey): + OnPropertyChanged(nameof(SelectPrevItemHotkey)); + break; + case nameof(Settings.SelectPrevItemHotkey2): + OnPropertyChanged(nameof(SelectPrevItemHotkey2)); + break; + case nameof(Settings.SelectNextPageHotkey): + OnPropertyChanged(nameof(SelectNextPageHotkey)); + break; + case nameof(Settings.SelectPrevPageHotkey): + OnPropertyChanged(nameof(SelectPrevPageHotkey)); + break; + case nameof(Settings.OpenContextMenuHotkey): + OnPropertyChanged(nameof(OpenContextMenuHotkey)); + break; + case nameof(Settings.SettingWindowHotkey): + OnPropertyChanged(nameof(SettingWindowHotkey)); + break; } }; @@ -93,15 +143,21 @@ namespace Flow.Launcher.ViewModel ContextMenu = new ResultsViewModel(Settings) { - LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand + LeftClickResultCommand = OpenResultCommand, + RightClickResultCommand = LoadContextMenuCommand, + IsPreviewOn = Settings.AlwaysPreview }; Results = new ResultsViewModel(Settings) { - LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand + LeftClickResultCommand = OpenResultCommand, + RightClickResultCommand = LoadContextMenuCommand, + IsPreviewOn = Settings.AlwaysPreview }; History = new ResultsViewModel(Settings) { - LeftClickResultCommand = OpenResultCommand, RightClickResultCommand = LoadContextMenuCommand + LeftClickResultCommand = OpenResultCommand, + RightClickResultCommand = LoadContextMenuCommand, + IsPreviewOn = Settings.AlwaysPreview }; _selectedResults = Results; @@ -226,6 +282,32 @@ namespace Flow.Launcher.ViewModel } } + [RelayCommand] + public void ReverseHistory() + { + if (_history.Items.Count > 0) + { + ChangeQueryText(_history.Items[_history.Items.Count - lastHistoryIndex].Query.ToString()); + if (lastHistoryIndex < _history.Items.Count) + { + lastHistoryIndex++; + } + } + } + + [RelayCommand] + public void ForwardHistory() + { + if (_history.Items.Count > 0) + { + ChangeQueryText(_history.Items[_history.Items.Count - lastHistoryIndex].Query.ToString()); + if (lastHistoryIndex > 1) + { + lastHistoryIndex--; + } + } + } + [RelayCommand] private void LoadContextMenu() { @@ -316,6 +398,7 @@ namespace Flow.Launcher.ViewModel { _userSelectedRecord.Add(result); _history.Add(result.OriginQuery.RawQuery); + lastHistoryIndex = 1; } if (hideWindow) @@ -324,6 +407,10 @@ namespace Flow.Launcher.ViewModel } } + #endregion + + #region BasicCommands + [RelayCommand] private void OpenSetting() { @@ -342,6 +429,13 @@ namespace Flow.Launcher.ViewModel SelectedResults.SelectFirstResult(); } + [RelayCommand] + private void SelectLastResult() + { + SelectedResults.SelectLastResult(); + } + + [RelayCommand] private void SelectPrevPage() { @@ -357,7 +451,18 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void SelectPrevItem() { - SelectedResults.SelectPrevResult(); + if (_history.Items.Count > 0 + && QueryText == string.Empty + && SelectedIsFromQueryResults()) + { + lastHistoryIndex = 1; + ReverseHistory(); + } + else + { + SelectedResults.SelectPrevResult(); + } + } [RelayCommand] @@ -384,7 +489,7 @@ namespace Flow.Launcher.ViewModel { GameModeStatus = !GameModeStatus; } - + [RelayCommand] public void CopyAlternative() { @@ -403,7 +508,6 @@ namespace Flow.Launcher.ViewModel public Settings Settings { get; } public string ClockText { get; private set; } public string DateText { get; private set; } - public CultureInfo Culture => CultureInfo.DefaultThreadCurrentCulture; private async Task RegisterClockAndDateUpdateAsync() { @@ -412,9 +516,9 @@ namespace Flow.Launcher.ViewModel while (await timer.WaitForNextTickAsync().ConfigureAwait(false)) { if (Settings.UseClock) - ClockText = DateTime.Now.ToString(Settings.TimeFormat, Culture); + ClockText = DateTime.Now.ToString(Settings.TimeFormat, CultureInfo.CurrentCulture); if (Settings.UseDate) - DateText = DateTime.Now.ToString(Settings.DateFormat, Culture); + DateText = DateTime.Now.ToString(Settings.DateFormat, CultureInfo.CurrentCulture); } } @@ -442,17 +546,9 @@ namespace Flow.Launcher.ViewModel [RelayCommand] private void IncreaseWidth() { - if (MainWindowWidth + 100 > 1920 || Settings.WindowSize == 1920) - { - Settings.WindowSize = 1920; - } - else - { - Settings.WindowSize += 100; - Settings.WindowLeft -= 50; - } - - OnPropertyChanged(); + Settings.WindowSize += 100; + Settings.WindowLeft -= 50; + OnPropertyChanged(nameof(MainWindowWidth)); } [RelayCommand] @@ -468,7 +564,7 @@ namespace Flow.Launcher.ViewModel Settings.WindowSize -= 100; } - OnPropertyChanged(); + OnPropertyChanged(nameof(MainWindowWidth)); } [RelayCommand] @@ -489,52 +585,6 @@ namespace Flow.Launcher.ViewModel Settings.MaxResultsToShow -= 1; } - [RelayCommand] - public void TogglePreview() - { - if (!PreviewVisible) - { - ShowPreview(); - } - else - { - HidePreview(); - } - } - - private void ShowPreview() - { - ResultAreaColumn = 1; - PreviewVisible = true; - Results.SelectedItem?.LoadPreviewImage(); - } - - private void HidePreview() - { - ResultAreaColumn = 3; - PreviewVisible = false; - } - - public void ResetPreview() - { - if (Settings.AlwaysPreview == true) - { - ShowPreview(); - } - else - { - HidePreview(); - } - } - - private void UpdatePreview() - { - if (PreviewVisible) - { - Results.SelectedItem?.LoadPreviewImage(); - } - } - /// /// we need move cursor to end when we manually changed query /// but we don't want to move cursor to end when query is updated from TextBox @@ -574,12 +624,27 @@ namespace Flow.Launcher.ViewModel get { return _selectedResults; } set { + var isReturningFromContextMenu = ContextMenuSelected(); _selectedResults = value; if (SelectedIsFromQueryResults()) { ContextMenu.Visibility = Visibility.Collapsed; History.Visibility = Visibility.Collapsed; - ChangeQueryText(_queryTextBeforeLeaveResults); + + // QueryText setter (used in ChangeQueryText) runs the query again, resetting the selected + // result from the one that was selected before going into the context menu to the first result. + // The code below correctly restores QueryText and puts the text caret at the end without + // running the query again when returning from the context menu. + if (isReturningFromContextMenu) + { + _queryText = _queryTextBeforeLeaveResults; + OnPropertyChanged(nameof(QueryText)); + QueryTextCursorMovedToEnd = true; + } + else + { + ChangeQueryText(_queryTextBeforeLeaveResults); + } } else { @@ -620,40 +685,260 @@ namespace Flow.Launcher.ViewModel public double MainWindowWidth { get => Settings.WindowSize; - set => Settings.WindowSize = value; + set + { + if (!MainWindowVisibilityStatus) return; + Settings.WindowSize = value; + } + } + + public double MainWindowHeight + { + get => Settings.WindowHeightSize; + set => Settings.WindowHeightSize = value; + } + + public double QueryBoxFontSize + { + get => Settings.QueryBoxFontSize; + set => Settings.QueryBoxFontSize = value; + } + + public double ItemHeightSize + { + get => Settings.ItemHeightSize; + set => Settings.ItemHeightSize = value; + } + + public double ResultItemFontSize + { + get => Settings.ResultItemFontSize; + set => Settings.ResultItemFontSize = value; + } + + public double ResultSubItemFontSize + { + get => Settings.ResultSubItemFontSize; + set => Settings.ResultSubItemFontSize = value; } public string PluginIconPath { get; set; } = null; public string OpenResultCommandModifiers => Settings.OpenResultModifiers; - public string PreviewHotkey + public string VerifyOrSetDefaultHotkey(string hotkey, string defaultHotkey) { - get + try { - // TODO try to patch issue #1755 - // Added in v1.14.0, remove after v1.16.0. - try - { - var converter = new KeyGestureConverter(); - var key = (KeyGesture)converter.ConvertFromString(Settings.PreviewHotkey); - } - catch (Exception e) when (e is NotSupportedException || e is InvalidEnumArgumentException) - { - Settings.PreviewHotkey = "F1"; - } - - return Settings.PreviewHotkey; + var converter = new KeyGestureConverter(); + var key = (KeyGesture)converter.ConvertFromString(hotkey); } + catch (Exception e) when (e is NotSupportedException || e is InvalidEnumArgumentException) + { + return defaultHotkey; + } + + return hotkey; } + public string PreviewHotkey => VerifyOrSetDefaultHotkey(Settings.PreviewHotkey, "F1"); + public string AutoCompleteHotkey => VerifyOrSetDefaultHotkey(Settings.AutoCompleteHotkey, "Ctrl+Tab"); + public string AutoCompleteHotkey2 => VerifyOrSetDefaultHotkey(Settings.AutoCompleteHotkey2, ""); + public string SelectNextItemHotkey => VerifyOrSetDefaultHotkey(Settings.SelectNextItemHotkey, "Tab"); + public string SelectNextItemHotkey2 => VerifyOrSetDefaultHotkey(Settings.SelectNextItemHotkey2, ""); + public string SelectPrevItemHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevItemHotkey, "Shift+Tab"); + public string SelectPrevItemHotkey2 => VerifyOrSetDefaultHotkey(Settings.SelectPrevItemHotkey2, ""); + public string SelectNextPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectNextPageHotkey, ""); + public string SelectPrevPageHotkey => VerifyOrSetDefaultHotkey(Settings.SelectPrevPageHotkey, ""); + public string OpenContextMenuHotkey => VerifyOrSetDefaultHotkey(Settings.OpenContextMenuHotkey, "Ctrl+O"); + public string SettingWindowHotkey => VerifyOrSetDefaultHotkey(Settings.SettingWindowHotkey, "Ctrl+I"); + public string CycleHistoryUpHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryUpHotkey, "Alt+Up"); + public string CycleHistoryDownHotkey => VerifyOrSetDefaultHotkey(Settings.CycleHistoryDownHotkey, "Alt+Down"); + + public string Image => Constant.QueryTextBoxIconImagePath; public bool StartWithEnglishMode => Settings.AlwaysStartEn; + + #endregion - public bool PreviewVisible { get; set; } = false; + #region Preview - public int ResultAreaColumn { get; set; } = 1; + public bool InternalPreviewVisible + { + get + { + if (ResultAreaColumn == ResultAreaColumnPreviewShown) + return true; + + if (ResultAreaColumn == ResultAreaColumnPreviewHidden) + return false; +#if DEBUG + throw new NotImplementedException("ResultAreaColumn should match ResultAreaColumnPreviewShown/ResultAreaColumnPreviewHidden value"); +#else + Log.Error("MainViewModel", "ResultAreaColumnPreviewHidden/ResultAreaColumnPreviewShown int value not implemented", "InternalPreviewVisible"); +#endif + return false; + } + } + + private static readonly int ResultAreaColumnPreviewShown = 1; + + private static readonly int ResultAreaColumnPreviewHidden = 3; + + public int ResultAreaColumn { get; set; } = ResultAreaColumnPreviewShown; + + // This is not a reliable indicator of whether external preview is visible due to the + // ability of manually closing/exiting the external preview program which, does not inform flow that + // preview is no longer available. + public bool ExternalPreviewVisible { get; set; } = false; + + private void ShowPreview() + { + var useExternalPreview = PluginManager.UseExternalPreview(); + + switch (useExternalPreview) + { + case true + when CanExternalPreviewSelectedResult(out var path): + // Internal preview may still be on when user switches to external + if (InternalPreviewVisible) + HideInternalPreview(); + OpenExternalPreview(path); + break; + + case true + when !CanExternalPreviewSelectedResult(out var _): + if (ExternalPreviewVisible) + CloseExternalPreview(); + ShowInternalPreview(); + break; + + case false: + ShowInternalPreview(); + break; + } + } + + private void HidePreview() + { + if (PluginManager.UseExternalPreview()) + CloseExternalPreview(); + + if (InternalPreviewVisible) + HideInternalPreview(); + } + + [RelayCommand] + private void TogglePreview() + { + if (InternalPreviewVisible || ExternalPreviewVisible) + { + HidePreview(); + } + else + { + ShowPreview(); + } + } + + private void ToggleInternalPreview() + { + if (!InternalPreviewVisible) + { + ShowInternalPreview(); + } + else + { + HideInternalPreview(); + } + } + + private void OpenExternalPreview(string path, bool sendFailToast = true) + { + _ = PluginManager.OpenExternalPreviewAsync(path, sendFailToast).ConfigureAwait(false); + ExternalPreviewVisible = true; + } + + private void CloseExternalPreview() + { + _ = PluginManager.CloseExternalPreviewAsync().ConfigureAwait(false); + ExternalPreviewVisible = false; + } + + private void SwitchExternalPreview(string path, bool sendFailToast = true) + { + _ = PluginManager.SwitchExternalPreviewAsync(path,sendFailToast).ConfigureAwait(false); + } + + private void ShowInternalPreview() + { + ResultAreaColumn = ResultAreaColumnPreviewShown; + Results.SelectedItem?.LoadPreviewImage(); + } + + private void HideInternalPreview() + { + ResultAreaColumn = ResultAreaColumnPreviewHidden; + } + + public void ResetPreview() + { + switch (Settings.AlwaysPreview) + { + case true + when PluginManager.AllowAlwaysPreview() && CanExternalPreviewSelectedResult(out var path): + OpenExternalPreview(path); + break; + + case true: + ShowInternalPreview(); + break; + + case false: + HidePreview(); + break; + } + } + + private void UpdatePreview() + { + switch (PluginManager.UseExternalPreview()) + { + case true + when CanExternalPreviewSelectedResult(out var path): + if (ExternalPreviewVisible) + { + SwitchExternalPreview(path, false); + } + else if (InternalPreviewVisible) + { + HideInternalPreview(); + OpenExternalPreview(path); + } + break; + + case true + when !CanExternalPreviewSelectedResult(out var _): + if (ExternalPreviewVisible) + { + CloseExternalPreview(); + ShowInternalPreview(); + } + break; + + case false + when InternalPreviewVisible: + Results.SelectedItem?.LoadPreviewImage(); + break; + } + } + + private bool CanExternalPreviewSelectedResult(out string path) + { + path = Results.SelectedItem?.Result?.Preview.FilePath; + return !string.IsNullOrEmpty(path); + } #endregion @@ -1071,11 +1356,15 @@ namespace Flow.Launcher.ViewModel public async void Hide() { + lastHistoryIndex = 1; // Trick for no delay MainWindowOpacity = 0; lastContextMenuResult = new Result(); lastContextMenuResults = new List(); + if (ExternalPreviewVisible) + CloseExternalPreview(); + if (!SelectedIsFromQueryResults()) { SelectedResults = Results; diff --git a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs index bc98efabc..513d0443f 100644 --- a/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginStoreItemViewModel.cs @@ -1,12 +1,15 @@ using System; +using System.Linq; +using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.ExternalPlugins; using Flow.Launcher.Core.Plugin; using Flow.Launcher.Plugin; namespace Flow.Launcher.ViewModel { - public class PluginStoreItemViewModel : BaseModel + public partial class PluginStoreItemViewModel : BaseModel { + private PluginPair PluginManagerData => PluginManager.GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7"); public PluginStoreItemViewModel(UserPlugin plugin) { _plugin = plugin; @@ -26,19 +29,13 @@ namespace Flow.Launcher.ViewModel public string IcoPath => _plugin.IcoPath; public bool LabelInstalled => PluginManager.GetPluginForId(_plugin.ID) != null; - public bool LabelUpdate => LabelInstalled && VersionConvertor(_plugin.Version) > VersionConvertor(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version); + public bool LabelUpdate => LabelInstalled && new Version(_plugin.Version) > new Version(PluginManager.GetPluginForId(_plugin.ID).Metadata.Version); internal const string None = "None"; internal const string RecentlyUpdated = "RecentlyUpdated"; internal const string NewRelease = "NewRelease"; internal const string Installed = "Installed"; - public Version VersionConvertor(string version) - { - Version ResultVersion = new Version(version); - return ResultVersion; - } - public string Category { get @@ -60,5 +57,13 @@ namespace Flow.Launcher.ViewModel return category; } } + + [RelayCommand] + private void ShowCommandQuery(string action) + { + var actionKeyword = PluginManagerData.Metadata.ActionKeywords.Any() ? PluginManagerData.Metadata.ActionKeywords[0] + " " : String.Empty; + App.API.ChangeQuery($"{actionKeyword}{action} {_plugin.Name}"); + App.API.ShowMainWindow(); + } } } diff --git a/Flow.Launcher/ViewModel/PluginViewModel.cs b/Flow.Launcher/ViewModel/PluginViewModel.cs index d2919507d..4ce8bd470 100644 --- a/Flow.Launcher/ViewModel/PluginViewModel.cs +++ b/Flow.Launcher/ViewModel/PluginViewModel.cs @@ -1,4 +1,5 @@ -using System.Windows; +using System.Linq; +using System.Windows; using System.Windows.Media; using Flow.Launcher.Plugin; using Flow.Launcher.Infrastructure.Image; @@ -6,6 +7,7 @@ using Flow.Launcher.Core.Plugin; using System.Windows.Controls; using CommunityToolkit.Mvvm.Input; using Flow.Launcher.Core.Resource; +using Flow.Launcher.Resources.Controls; namespace Flow.Launcher.ViewModel { @@ -26,6 +28,21 @@ namespace Flow.Launcher.ViewModel } } + private string PluginManagerActionKeyword + { + get + { + var keyword = PluginManager + .GetPluginForId("9f8f9b14-2518-4907-b211-35ab6290dee7") + .Metadata.ActionKeywords.FirstOrDefault(); + return keyword switch + { + null or "*" => string.Empty, + _ => keyword + }; + } + } + private async void LoadIconAsync() { @@ -46,7 +63,11 @@ namespace Flow.Launcher.ViewModel public bool PluginState { get => !PluginPair.Metadata.Disabled; - set => PluginPair.Metadata.Disabled = !value; + set + { + PluginPair.Metadata.Disabled = !value; + PluginSettingsObject.Disabled = !value; + } } public bool IsExpanded { @@ -62,11 +83,19 @@ namespace Flow.Launcher.ViewModel private Control _settingControl; private bool _isExpanded; + + private Control _bottomPart1; + public Control BottomPart1 => IsExpanded ? _bottomPart1 ??= new InstalledPluginDisplayKeyword() : null; + + private Control _bottomPart2; + public Control BottomPart2 => IsExpanded ? _bottomPart2 ??= new InstalledPluginDisplayBottomData() : null; + + public bool HasSettingControl => PluginPair.Plugin is ISettingProvider; public Control SettingControl => IsExpanded ? _settingControl ??= PluginPair.Plugin is not ISettingProvider settingProvider - ? new Control() + ? null : settingProvider.CreateSettingPanel() : null; private ImageSource _image = ImageLoader.MissingImage; @@ -78,6 +107,7 @@ namespace Flow.Launcher.ViewModel public string InitAndQueryTime => InternationalizationManager.Instance.GetTranslation("plugin_init_time") + " " + PluginPair.Metadata.InitTime + "ms, " + InternationalizationManager.Instance.GetTranslation("plugin_query_time") + " " + PluginPair.Metadata.AvgQueryTime + "ms"; public string ActionKeywordsText => string.Join(Query.ActionKeywordSeparator, PluginPair.Metadata.ActionKeywords); public int Priority => PluginPair.Metadata.Priority; + public Infrastructure.UserSettings.Plugin PluginSettingsObject { get; set; } public void ChangeActionKeyword(string newActionKeyword, string oldActionKeyword) { @@ -88,13 +118,14 @@ namespace Flow.Launcher.ViewModel public void ChangePriority(int newPriority) { PluginPair.Metadata.Priority = newPriority; + PluginSettingsObject.Priority = newPriority; OnPropertyChanged(nameof(Priority)); } [RelayCommand] private void EditPluginPriority() { - PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair.Metadata.ID, this); + PriorityChangeWindow priorityChangeWindow = new PriorityChangeWindow(PluginPair. Metadata.ID, this); priorityChangeWindow.ShowDialog(); } @@ -106,6 +137,19 @@ namespace Flow.Launcher.ViewModel PluginManager.API.OpenDirectory(directory); } + [RelayCommand] + private void OpenSourceCodeLink() + { + PluginManager.API.OpenUrl(PluginPair.Metadata.Website); + } + + [RelayCommand] + private void OpenDeletePluginWindow() + { + PluginManager.API.ChangeQuery($"{PluginManagerActionKeyword} uninstall {PluginPair.Metadata.Name}".Trim(), true); + PluginManager.API.ShowMainWindow(); + } + public static bool IsActionKeywordRegistered(string newActionKeyword) => PluginManager.ActionKeywordRegistered(newActionKeyword); [RelayCommand] diff --git a/Flow.Launcher/ViewModel/ResultViewModel.cs b/Flow.Launcher/ViewModel/ResultViewModel.cs index 3f204c16c..5130e7eba 100644 --- a/Flow.Launcher/ViewModel/ResultViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultViewModel.cs @@ -29,7 +29,7 @@ namespace Flow.Launcher.ViewModel if (Result.Glyph is { FontFamily: not null } glyph) { - // Checks if it's a system installed font, which does not require path to be provided. + // Checks if it's a system installed font, which does not require path to be provided. if (glyph.FontFamily.EndsWith(".ttf") || glyph.FontFamily.EndsWith(".otf")) { string fontFamilyPath = glyph.FontFamily; @@ -64,7 +64,7 @@ namespace Flow.Launcher.ViewModel } - private Settings Settings { get; } + public Settings Settings { get; } public Visibility ShowOpenResultHotkey => Settings.ShowOpenResultHotkey ? Visibility.Visible : Visibility.Collapsed; diff --git a/Flow.Launcher/ViewModel/ResultsViewModel.cs b/Flow.Launcher/ViewModel/ResultsViewModel.cs index d02dc9bd5..107372e01 100644 --- a/Flow.Launcher/ViewModel/ResultsViewModel.cs +++ b/Flow.Launcher/ViewModel/ResultsViewModel.cs @@ -1,4 +1,5 @@ -using Flow.Launcher.Infrastructure.UserSettings; +using System; +using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; using System.Collections.Generic; using System.Collections.Specialized; @@ -32,9 +33,15 @@ namespace Flow.Launcher.ViewModel _settings = settings; _settings.PropertyChanged += (s, e) => { - if (e.PropertyName == nameof(_settings.MaxResultsToShow)) + switch (e.PropertyName) { - OnPropertyChanged(nameof(MaxHeight)); + case nameof(_settings.MaxResultsToShow): + OnPropertyChanged(nameof(MaxHeight)); + break; + case nameof(_settings.ItemHeightSize): + OnPropertyChanged(nameof(ItemHeightSize)); + OnPropertyChanged(nameof(MaxHeight)); + break; } }; } @@ -43,14 +50,37 @@ namespace Flow.Launcher.ViewModel #region Properties - public double MaxHeight => MaxResults * (double)Application.Current.FindResource("ResultItemHeight")!; + public bool IsPreviewOn { get; set; } + + public double MaxHeight + { + get + { + var newResultsCount = MaxResults; + if (IsPreviewOn) + { + newResultsCount = (int)Math.Ceiling(380 / _settings.ItemHeightSize); + if (newResultsCount < MaxResults) + { + newResultsCount = MaxResults; + } + } + return newResultsCount * _settings.ItemHeightSize; + } + } + + public double ItemHeightSize + { + get => _settings.ItemHeightSize; + set => _settings.ItemHeightSize = value; + } public int SelectedIndex { get; set; } public ResultViewModel SelectedItem { get; set; } public Thickness Margin { get; set; } public Visibility Visibility { get; set; } = Visibility.Collapsed; - + public ICommand RightClickResultCommand { get; init; } public ICommand LeftClickResultCommand { get; init; } @@ -117,6 +147,11 @@ namespace Flow.Launcher.ViewModel SelectedIndex = NewIndex(0); } + public void SelectLastResult() + { + SelectedIndex = NewIndex(Results.Count - 1); + } + public void Clear() { lock (_collectionLock) diff --git a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs index 86a25ffb1..04dd6312b 100644 --- a/Flow.Launcher/ViewModel/SettingWindowViewModel.cs +++ b/Flow.Launcher/ViewModel/SettingWindowViewModel.cs @@ -1,997 +1,66 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using Flow.Launcher.Core; +using Flow.Launcher.Core; using Flow.Launcher.Core.Configuration; -using Flow.Launcher.Core.ExternalPlugins; -using Flow.Launcher.Core.Plugin; -using Flow.Launcher.Core.Resource; -using Flow.Launcher.Helper; -using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Storage; using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin; -using Flow.Launcher.Plugin.SharedModels; -using System.Collections.ObjectModel; -using CommunityToolkit.Mvvm.Input; -using System.Globalization; -namespace Flow.Launcher.ViewModel +namespace Flow.Launcher.ViewModel; + +public class SettingWindowViewModel : BaseModel { - public partial class SettingWindowViewModel : BaseModel + private readonly FlowLauncherJsonStorage _storage; + + public Updater Updater { get; } + + public IPortable Portable { get; } + + public Settings Settings { get; } + + public SettingWindowViewModel(Updater updater, IPortable portable) { - private readonly Updater _updater; - private readonly IPortable _portable; - private readonly FlowLauncherJsonStorage _storage; - - public SettingWindowViewModel(Updater updater, IPortable portable) - { - _updater = updater; - _portable = portable; - _storage = new FlowLauncherJsonStorage(); - Settings = _storage.Load(); - Settings.PropertyChanged += (s, e) => - { - switch (e.PropertyName) - { - case nameof(Settings.ActivateTimes): - OnPropertyChanged(nameof(ActivatedTimes)); - break; - case nameof(Settings.WindowSize): - OnPropertyChanged(nameof(WindowWidthSize)); - break; - case nameof(Settings.UseDate): - case nameof(Settings.DateFormat): - OnPropertyChanged(nameof(DateText)); - break; - case nameof(Settings.UseClock): - case nameof(Settings.TimeFormat): - OnPropertyChanged(nameof(ClockText)); - break; - case nameof(Settings.Language): - OnPropertyChanged(nameof(ClockText)); - OnPropertyChanged(nameof(DateText)); - OnPropertyChanged(nameof(AlwaysPreviewToolTip)); - break; - case nameof(Settings.PreviewHotkey): - OnPropertyChanged(nameof(AlwaysPreviewToolTip)); - break; - case nameof(Settings.SoundVolume): - OnPropertyChanged(nameof(SoundEffectVolume)); - break; - } - }; - - } - - public Settings Settings { get; set; } - - public async void UpdateApp() - { - await _updater.UpdateAppAsync(App.API, false); - } - - public bool AutoUpdates - { - get => Settings.AutoUpdates; - set - { - Settings.AutoUpdates = value; - - if (value) - { - UpdateApp(); - } - } - } - - public CultureInfo Culture => CultureInfo.DefaultThreadCurrentCulture; - - public bool StartFlowLauncherOnSystemStartup - { - get => Settings.StartFlowLauncherOnSystemStartup; - set - { - Settings.StartFlowLauncherOnSystemStartup = value; - - try - { - if (value) - AutoStartup.Enable(); - else - AutoStartup.Disable(); - } - catch (Exception e) - { - Notification.Show(InternationalizationManager.Instance.GetTranslation("setAutoStartFailed"), e.Message); - } - } - } - - // This is only required to set at startup. When portable mode enabled/disabled a restart is always required - private bool _portableMode = DataLocation.PortableDataLocationInUse(); - public bool PortableMode - { - get => _portableMode; - set - { - if (!_portable.CanUpdatePortability()) - return; - - if (DataLocation.PortableDataLocationInUse()) - { - _portable.DisablePortableMode(); - } - else - { - _portable.EnablePortableMode(); - } - } - } - - /// - /// Save Flow settings. Plugins settings are not included. - /// - public void Save() - { - foreach (var vm in PluginViewModels) - { - var id = vm.PluginPair.Metadata.ID; - - Settings.PluginSettings.Plugins[id].Disabled = vm.PluginPair.Metadata.Disabled; - Settings.PluginSettings.Plugins[id].Priority = vm.Priority; - } - - _storage.Save(); - } - - public string GetFileFromDialog(string title, string filter = "") - { - var dlg = new System.Windows.Forms.OpenFileDialog - { - InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), - Multiselect = false, - CheckFileExists = true, - CheckPathExists = true, - Title = title, - Filter = filter - }; - - var result = dlg.ShowDialog(); - if (result == System.Windows.Forms.DialogResult.OK) - { - return dlg.FileName; - } - else - { - return string.Empty; - } - } - - #region general - - // todo a better name? - public class LastQueryMode : BaseModel - { - public string Display { get; set; } - public Infrastructure.UserSettings.LastQueryMode Value { get; set; } - } - - private List _lastQueryModes = new List(); - public List LastQueryModes - { - get - { - if (_lastQueryModes.Count == 0) - { - _lastQueryModes = InitLastQueryModes(); - } - return _lastQueryModes; - } - } - - private List InitLastQueryModes() - { - var modes = new List(); - var enums = (Infrastructure.UserSettings.LastQueryMode[])Enum.GetValues(typeof(Infrastructure.UserSettings.LastQueryMode)); - foreach (var e in enums) - { - var key = $"LastQuery{e}"; - var display = _translater.GetTranslation(key); - var m = new LastQueryMode - { - Display = display, Value = e, - }; - modes.Add(m); - } - return modes; - } - - private void UpdateLastQueryModeDisplay() - { - foreach (var item in LastQueryModes) - { - item.Display = _translater.GetTranslation($"LastQuery{item.Value}"); - } - } - - public string Language - { - get - { - return Settings.Language; - } - set - { - InternationalizationManager.Instance.ChangeLanguage(value); - - if (InternationalizationManager.Instance.PromptShouldUsePinyin(value)) - ShouldUsePinyin = true; - - UpdateLastQueryModeDisplay(); - } - } - - public bool ShouldUsePinyin - { - get - { - return Settings.ShouldUsePinyin; - } - set - { - Settings.ShouldUsePinyin = value; - } - } - - public List QuerySearchPrecisionStrings - { - get - { - var precisionStrings = new List(); - - var enumList = Enum.GetValues(typeof(SearchPrecisionScore)).Cast().ToList(); - - enumList.ForEach(x => precisionStrings.Add(x.ToString())); - - return precisionStrings; - } - } - - public List OpenResultModifiersList => new List - { - KeyConstant.Alt, - KeyConstant.Ctrl, - $"{KeyConstant.Ctrl}+{KeyConstant.Alt}" - }; - private Internationalization _translater => InternationalizationManager.Instance; - public List Languages => _translater.LoadAvailableLanguages(); - public IEnumerable MaxResultsRange => Enumerable.Range(2, 16); - - public string AlwaysPreviewToolTip => string.Format(_translater.GetTranslation("AlwaysPreviewToolTip"), Settings.PreviewHotkey); - - public string TestProxy() - { - var proxyServer = Settings.Proxy.Server; - var proxyUserName = Settings.Proxy.UserName; - if (string.IsNullOrEmpty(proxyServer)) - { - return InternationalizationManager.Instance.GetTranslation("serverCantBeEmpty"); - } - if (Settings.Proxy.Port <= 0) - { - return InternationalizationManager.Instance.GetTranslation("portCantBeEmpty"); - } - - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(_updater.GitHubRepository); - - if (string.IsNullOrEmpty(proxyUserName) || string.IsNullOrEmpty(Settings.Proxy.Password)) - { - request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port); - } - else - { - request.Proxy = new WebProxy(proxyServer, Settings.Proxy.Port) - { - Credentials = new NetworkCredential(proxyUserName, Settings.Proxy.Password) - }; - } - try - { - var response = (HttpWebResponse)request.GetResponse(); - if (response.StatusCode == HttpStatusCode.OK) - { - return InternationalizationManager.Instance.GetTranslation("proxyIsCorrect"); - } - else - { - return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed"); - } - } - catch - { - return InternationalizationManager.Instance.GetTranslation("proxyConnectFailed"); - } - } - - #endregion - - #region plugin - - public static string Plugin => @"https://github.com/Flow-Launcher/Flow.Launcher.PluginsManifest"; - public PluginViewModel SelectedPlugin { get; set; } - - public IList PluginViewModels - { - get => PluginManager.AllPlugins - .OrderBy(x => x.Metadata.Disabled) - .ThenBy(y => y.Metadata.Name) - .Select(p => new PluginViewModel - { - PluginPair = p - }) - .ToList(); - } - - public IList ExternalPlugins - { - get - { - return LabelMaker(PluginsManifest.UserPlugins); - } - } - - private IList LabelMaker(IList list) - { - return list.Select(p => new PluginStoreItemViewModel(p)) - .OrderByDescending(p => p.Category == PluginStoreItemViewModel.NewRelease) - .ThenByDescending(p => p.Category == PluginStoreItemViewModel.RecentlyUpdated) - .ThenByDescending(p => p.Category == PluginStoreItemViewModel.None) - .ThenByDescending(p => p.Category == PluginStoreItemViewModel.Installed) - .ToList(); - } - - public Control SettingProvider - { - get - { - var settingProvider = SelectedPlugin.PluginPair.Plugin as ISettingProvider; - if (settingProvider != null) - { - var control = settingProvider.CreateSettingPanel(); - control.HorizontalAlignment = HorizontalAlignment.Stretch; - control.VerticalAlignment = VerticalAlignment.Stretch; - return control; - } - else - { - return new Control(); - } - } - } - - [RelayCommand] - private async Task RefreshExternalPluginsAsync() - { - await PluginsManifest.UpdateManifestAsync(); - OnPropertyChanged(nameof(ExternalPlugins)); - } - - - - internal void DisplayPluginQuery(string queryToDisplay, PluginPair plugin, int actionKeywordPosition = 0) - { - var actionKeyword = plugin.Metadata.ActionKeywords.Count == 0 - ? string.Empty - : plugin.Metadata.ActionKeywords[actionKeywordPosition]; - - App.API.ChangeQuery($"{actionKeyword} {queryToDisplay}"); - App.API.ShowMainWindow(); - } - - #endregion - - #region theme - - public static string Theme => @"https://flowlauncher.com/docs/#/how-to-create-a-theme"; - public static string ThemeGallery => @"https://github.com/Flow-Launcher/Flow.Launcher/discussions/1438"; - - public string SelectedTheme - { - get { return Settings.Theme; } - set - { - ThemeManager.Instance.ChangeTheme(value); - - if (ThemeManager.Instance.BlurEnabled && Settings.UseDropShadowEffect) - DropShadowEffect = false; - } - } - - public List Themes - => ThemeManager.Instance.LoadAvailableThemes().Select(Path.GetFileNameWithoutExtension).ToList(); - - public bool DropShadowEffect - { - get { return Settings.UseDropShadowEffect; } - set - { - if (ThemeManager.Instance.BlurEnabled && value) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("shadowEffectNotAllowed")); - return; - } - - if (value) - { - ThemeManager.Instance.AddDropShadowEffectToCurrentTheme(); - } - else - { - ThemeManager.Instance.RemoveDropShadowEffectFromCurrentTheme(); - } - - Settings.UseDropShadowEffect = value; - } - } - - public class ColorScheme - { - public string Display { get; set; } - public ColorSchemes Value { get; set; } - } - - public List ColorSchemes - { - get - { - List modes = new List(); - var enums = (ColorSchemes[])Enum.GetValues(typeof(ColorSchemes)); - foreach (var e in enums) - { - var key = $"ColorScheme{e}"; - var display = _translater.GetTranslation(key); - var m = new ColorScheme - { - Display = display, Value = e, - }; - modes.Add(m); - } - return modes; - } - } - - public class SearchWindowScreen - { - public string Display { get; set; } - public SearchWindowScreens Value { get; set; } - } - - public List SearchWindowScreens - { - get - { - List modes = new List(); - var enums = (SearchWindowScreens[])Enum.GetValues(typeof(SearchWindowScreens)); - foreach (var e in enums) - { - var key = $"SearchWindowScreen{e}"; - var display = _translater.GetTranslation(key); - var m = new SearchWindowScreen - { - Display = display, - Value = e, - }; - modes.Add(m); - } - return modes; - } - } - - public class SearchWindowAlign - { - public string Display { get; set; } - public SearchWindowAligns Value { get; set; } - } - - public List SearchWindowAligns - { - get - { - List modes = new List(); - var enums = (SearchWindowAligns[])Enum.GetValues(typeof(SearchWindowAligns)); - foreach (var e in enums) - { - var key = $"SearchWindowAlign{e}"; - var display = _translater.GetTranslation(key); - var m = new SearchWindowAlign - { - Display = display, Value = e, - }; - modes.Add(m); - } - return modes; - } - } - - public List ScreenNumbers - { - get - { - var screens = System.Windows.Forms.Screen.AllScreens; - var screenNumbers = new List(); - for (int i = 1; i <= screens.Length; i++) - { - screenNumbers.Add(i); - } - return screenNumbers; - } - } - - public List TimeFormatList { get; } = new() - { - "h:mm", - "hh:mm", - "H:mm", - "HH:mm", - "tt h:mm", - "tt hh:mm", - "h:mm tt", - "hh:mm tt", - "hh:mm:ss tt", - "HH:mm:ss" - }; - - public List DateFormatList { get; } = new() - { - "MM'/'dd dddd", - "MM'/'dd ddd", - "MM'/'dd", - "MM'-'dd", - "MMMM', 'dd", - "dd'/'MM", - "dd'-'MM", - "ddd MM'/'dd", - "dddd MM'/'dd", - "dddd", - "ddd dd'/'MM", - "dddd dd'/'MM", - "dddd dd', 'MMMM", - "dd', 'MMMM" - }; - - public string TimeFormat - { - get => Settings.TimeFormat; - set => Settings.TimeFormat = value; - } - - public string DateFormat - { - get => Settings.DateFormat; - set => Settings.DateFormat = value; - } - - public string ClockText => DateTime.Now.ToString(TimeFormat, Culture); - - public string DateText => DateTime.Now.ToString(DateFormat, Culture); - - public double WindowWidthSize - { - get => Settings.WindowSize; - set => Settings.WindowSize = value; - } - - public bool UseGlyphIcons - { - get => Settings.UseGlyphIcons; - set => Settings.UseGlyphIcons = value; - } - - public bool UseAnimation - { - get => Settings.UseAnimation; - set => Settings.UseAnimation = value; - } - - public class AnimationSpeed - { - public string Display { get; set; } - public AnimationSpeeds Value { get; set; } - } - - public List AnimationSpeeds - { - get - { - List speeds = new List(); - var enums = (AnimationSpeeds[])Enum.GetValues(typeof(AnimationSpeeds)); - foreach (var e in enums) - { - var key = $"AnimationSpeed{e}"; - var display = _translater.GetTranslation(key); - var m = new AnimationSpeed - { - Display = display, - Value = e, - }; - speeds.Add(m); - } - return speeds; - } - } - - public bool UseSound - { - get => Settings.UseSound; - set => Settings.UseSound = value; - } - - public double SoundEffectVolume - { - get => Settings.SoundVolume; - set => Settings.SoundVolume = value; - } - - public bool UseClock - { - get => Settings.UseClock; - set => Settings.UseClock = value; - } - - public bool UseDate - { - get => Settings.UseDate; - set => Settings.UseDate = value; - } - - public double SettingWindowWidth - { - get => Settings.SettingWindowWidth; - set => Settings.SettingWindowWidth = value; - } - - public double SettingWindowHeight - { - get => Settings.SettingWindowHeight; - set => Settings.SettingWindowHeight = value; - } - - public double SettingWindowTop - { - get => Settings.SettingWindowTop; - set => Settings.SettingWindowTop = value; - } - - public double SettingWindowLeft - { - get => Settings.SettingWindowLeft; - set => Settings.SettingWindowLeft = value; - } - - public Brush PreviewBackground - { - get - { - var wallpaper = WallpaperPathRetrieval.GetWallpaperPath(); - if (wallpaper != null && File.Exists(wallpaper)) - { - var memStream = new MemoryStream(File.ReadAllBytes(wallpaper)); - var bitmap = new BitmapImage(); - bitmap.BeginInit(); - bitmap.StreamSource = memStream; - bitmap.DecodePixelWidth = 800; - bitmap.DecodePixelHeight = 600; - bitmap.EndInit(); - var brush = new ImageBrush(bitmap) - { - Stretch = Stretch.UniformToFill - }; - return brush; - } - else - { - var wallpaperColor = WallpaperPathRetrieval.GetWallpaperColor(); - var brush = new SolidColorBrush(wallpaperColor); - return brush; - } - } - } - - public ResultsViewModel PreviewResults - { - get - { - var results = new List - { - new Result - { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleExplorer"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleExplorer"), - IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Explorer\Images\explorer.png") - }, - new Result - { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleWebSearch"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleWebSearch"), - IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.WebSearch\Images\web_search.png") - }, - new Result - { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProgram"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProgram"), - IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.Program\Images\program.png") - }, - new Result - { - Title = InternationalizationManager.Instance.GetTranslation("SampleTitleProcessKiller"), - SubTitle = InternationalizationManager.Instance.GetTranslation("SampleSubTitleProcessKiller"), - IcoPath = Path.Combine(Constant.ProgramDirectory, @"Plugins\Flow.Launcher.Plugin.ProcessKiller\Images\app.png") - } - }; - var vm = new ResultsViewModel(Settings); - vm.AddResults(results, "PREVIEW"); - return vm; - } - } - - public FontFamily SelectedQueryBoxFont - { - get - { - if (Fonts.SystemFontFamilies.Count(o => - o.FamilyNames.Values != null && - o.FamilyNames.Values.Contains(Settings.QueryBoxFont)) > 0) - { - var font = new FontFamily(Settings.QueryBoxFont); - return font; - } - else - { - var font = new FontFamily("Segoe UI"); - return font; - } - } - set - { - Settings.QueryBoxFont = value.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); - } - } - - public FamilyTypeface SelectedQueryBoxFontFaces - { - get - { - var typeface = SyntaxSugars.CallOrRescueDefault( - () => SelectedQueryBoxFont.ConvertFromInvariantStringsOrNormal( - Settings.QueryBoxFontStyle, - Settings.QueryBoxFontWeight, - Settings.QueryBoxFontStretch - )); - return typeface; - } - set - { - Settings.QueryBoxFontStretch = value.Stretch.ToString(); - Settings.QueryBoxFontWeight = value.Weight.ToString(); - Settings.QueryBoxFontStyle = value.Style.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); - } - } - - public FontFamily SelectedResultFont - { - get - { - if (Fonts.SystemFontFamilies.Count(o => - o.FamilyNames.Values != null && - o.FamilyNames.Values.Contains(Settings.ResultFont)) > 0) - { - var font = new FontFamily(Settings.ResultFont); - return font; - } - else - { - var font = new FontFamily("Segoe UI"); - return font; - } - } - set - { - Settings.ResultFont = value.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); - } - } - - public FamilyTypeface SelectedResultFontFaces - { - get - { - var typeface = SyntaxSugars.CallOrRescueDefault( - () => SelectedResultFont.ConvertFromInvariantStringsOrNormal( - Settings.ResultFontStyle, - Settings.ResultFontWeight, - Settings.ResultFontStretch - )); - return typeface; - } - set - { - Settings.ResultFontStretch = value.Stretch.ToString(); - Settings.ResultFontWeight = value.Weight.ToString(); - Settings.ResultFontStyle = value.Style.ToString(); - ThemeManager.Instance.ChangeTheme(Settings.Theme); - } - } - - public string ThemeImage => Constant.QueryTextBoxIconImagePath; - - #endregion - - #region hotkey - - public CustomPluginHotkey SelectedCustomPluginHotkey { get; set; } - - #endregion - - #region shortcut - - public ObservableCollection CustomShortcuts => Settings.CustomShortcuts; - - public ObservableCollection BuiltinShortcuts => Settings.BuiltinShortcuts; - - public CustomShortcutModel? SelectedCustomShortcut { get; set; } - - public void DeleteSelectedCustomShortcut() - { - var item = SelectedCustomShortcut; - if (item == null) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); - return; - } - - string deleteWarning = string.Format( - InternationalizationManager.Instance.GetTranslation("deleteCustomShortcutWarning"), - item.Key, item.Value); - if (MessageBox.Show(deleteWarning, InternationalizationManager.Instance.GetTranslation("delete"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - { - Settings.CustomShortcuts.Remove(item); - } - } - - public bool EditSelectedCustomShortcut() - { - var item = SelectedCustomShortcut; - if (item == null) - { - MessageBox.Show(InternationalizationManager.Instance.GetTranslation("pleaseSelectAnItem")); - return false; - } - - var shortcutSettingWindow = new CustomShortcutSetting(item.Key, item.Value, this); - if (shortcutSettingWindow.ShowDialog() == true) - { - // Fix un-selectable shortcut item after the first selection - // https://stackoverflow.com/questions/16789360/wpf-listbox-items-with-changing-hashcode - SelectedCustomShortcut = null; - item.Key = shortcutSettingWindow.Key; - item.Value = shortcutSettingWindow.Value; - SelectedCustomShortcut = item; - return true; - } - return false; - } - - public void AddCustomShortcut() - { - var shortcutSettingWindow = new CustomShortcutSetting(this); - if (shortcutSettingWindow.ShowDialog() == true) - { - var shortcut = new CustomShortcutModel(shortcutSettingWindow.Key, shortcutSettingWindow.Value); - Settings.CustomShortcuts.Add(shortcut); - } - } - - public bool ShortcutExists(string key) - { - return Settings.CustomShortcuts.Any(x => x.Key == key) || Settings.BuiltinShortcuts.Any(x => x.Key == key); - } - - #endregion - - #region about - - public string Website => Constant.Website; - public string SponsorPage => Constant.SponsorPage; - public string ReleaseNotes => _updater.GitHubRepository + @"/releases/latest"; - public string Documentation => Constant.Documentation; - public string Docs => Constant.Docs; - public string Github => Constant.GitHub; - public string Version - { - get - { - if (Constant.Version == "1.0.0") - { - return Constant.Dev; - } - else - { - return Constant.Version; - } - } - } - public string ActivatedTimes => string.Format(_translater.GetTranslation("about_activate_times"), Settings.ActivateTimes); - - public string CheckLogFolder - { - get - { - var logFiles = GetLogFiles(); - long size = logFiles.Sum(file => file.Length); - return string.Format("{0} ({1})", _translater.GetTranslation("clearlogfolder"), BytesToReadableString(size)); - } - } - - private static DirectoryInfo GetLogDir(string version = "") - { - return new DirectoryInfo(Path.Combine(DataLocation.DataDirectory(), Constant.Logs, version)); - } - - private static List GetLogFiles(string version = "") - { - return GetLogDir(version).EnumerateFiles("*", SearchOption.AllDirectories).ToList(); - } - - internal void ClearLogFolder() - { - var logDirectory = GetLogDir(); - var logFiles = GetLogFiles(); - - logFiles.ForEach(f => f.Delete()); - - logDirectory.EnumerateDirectories("*", SearchOption.TopDirectoryOnly) - .Where(dir => !Constant.Version.Equals(dir.Name)) - .ToList() - .ForEach(dir => dir.Delete()); - - OnPropertyChanged(nameof(CheckLogFolder)); - } - - internal void OpenLogFolder() - { - App.API.OpenDirectory(GetLogDir(Constant.Version).FullName); - } - - internal static string BytesToReadableString(long bytes) - { - const int scale = 1024; - string[] orders = new string[] - { - "GB", "MB", "KB", "B" - }; - long max = (long)Math.Pow(scale, orders.Length - 1); - - foreach (string order in orders) - { - if (bytes > max) - return string.Format("{0:##.##} {1}", decimal.Divide(bytes, max), order); - - max /= scale; - } - return "0 B"; - } - - #endregion + _storage = new FlowLauncherJsonStorage(); + + Updater = updater; + Portable = portable; + Settings = _storage.Load(); + } + + public async void UpdateApp() + { + await Updater.UpdateAppAsync(App.API, false); + } + + + + /// + /// Save Flow settings. Plugins settings are not included. + /// + public void Save() + { + _storage.Save(); + } + + public double SettingWindowWidth + { + get => Settings.SettingWindowWidth; + set => Settings.SettingWindowWidth = value; + } + + public double SettingWindowHeight + { + get => Settings.SettingWindowHeight; + set => Settings.SettingWindowHeight = value; + } + + public double? SettingWindowTop + { + get => Settings.SettingWindowTop; + set => Settings.SettingWindowTop = value; + } + + public double? SettingWindowLeft + { + get => Settings.SettingWindowLeft; + set => Settings.SettingWindowLeft = value; } } diff --git a/Flow.Launcher/WelcomeWindow.xaml b/Flow.Launcher/WelcomeWindow.xaml index c7820d436..003dac5bc 100644 --- a/Flow.Launcher/WelcomeWindow.xaml +++ b/Flow.Launcher/WelcomeWindow.xaml @@ -10,6 +10,10 @@ Title="{DynamicResource Welcome_Page1_Title}" Width="550" Height="650" + MinWidth="550" + MinHeight="650" + MaxWidth="550" + MaxHeight="650" Activated="OnActivated" Background="{DynamicResource Color00B}" Foreground="{DynamicResource PopupTextColor}" diff --git a/Flow.Launcher/app.manifest b/Flow.Launcher/app.manifest index 52d1c3932..c45508fcf 100644 --- a/Flow.Launcher/app.manifest +++ b/Flow.Launcher/app.manifest @@ -49,13 +49,12 @@ DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. --> - diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs index 09755fe0c..65757b802 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromeBookmarkLoader.cs @@ -3,23 +3,22 @@ using System; using System.Collections.Generic; using System.IO; -namespace Flow.Launcher.Plugin.BrowserBookmark -{ - public class ChromeBookmarkLoader : ChromiumBookmarkLoader - { - public override List GetBookmarks() - { - return LoadChromeBookmarks(); - } +namespace Flow.Launcher.Plugin.BrowserBookmark; - private List LoadChromeBookmarks() - { - var bookmarks = new List(); - var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium")); - return bookmarks; - } +public class ChromeBookmarkLoader : ChromiumBookmarkLoader +{ + public override List GetBookmarks() + { + return LoadChromeBookmarks(); } -} \ No newline at end of file + + private List LoadChromeBookmarks() + { + var bookmarks = new List(); + var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome\User Data"), "Google Chrome")); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Google\Chrome SxS\User Data"), "Google Chrome Canary")); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Chromium\User Data"), "Chromium")); + return bookmarks; + } +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs index 8ce597b30..48acf6109 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/ChromiumBookmarkLoader.cs @@ -4,91 +4,90 @@ using System.IO; using System.Text.Json; using Flow.Launcher.Infrastructure.Logger; -namespace Flow.Launcher.Plugin.BrowserBookmark +namespace Flow.Launcher.Plugin.BrowserBookmark; + +public abstract class ChromiumBookmarkLoader : IBookmarkLoader { - public abstract class ChromiumBookmarkLoader : IBookmarkLoader + public abstract List GetBookmarks(); + + protected List LoadBookmarks(string browserDataPath, string name) { - public abstract List GetBookmarks(); + var bookmarks = new List(); + if (!Directory.Exists(browserDataPath)) return bookmarks; + var paths = Directory.GetDirectories(browserDataPath); - protected List LoadBookmarks(string browserDataPath, string name) + foreach (var profile in paths) { - var bookmarks = new List(); - if (!Directory.Exists(browserDataPath)) return bookmarks; - var paths = Directory.GetDirectories(browserDataPath); + var bookmarkPath = Path.Combine(profile, "Bookmarks"); + if (!File.Exists(bookmarkPath)) + continue; - foreach (var profile in paths) - { - var bookmarkPath = Path.Combine(profile, "Bookmarks"); - if (!File.Exists(bookmarkPath)) - continue; + Main.RegisterBookmarkFile(bookmarkPath); - Main.RegisterBookmarkFile(bookmarkPath); + var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); + bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source)); + } - var source = name + (Path.GetFileName(profile) == "Default" ? "" : $" ({Path.GetFileName(profile)})"); - bookmarks.AddRange(LoadBookmarksFromFile(bookmarkPath, source)); - } + return bookmarks; + } + protected List LoadBookmarksFromFile(string path, string source) + { + var bookmarks = new List(); + + if (!File.Exists(path)) return bookmarks; - } - protected List LoadBookmarksFromFile(string path, string source) - { - var bookmarks = new List(); - - if (!File.Exists(path)) - return bookmarks; - - using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); - if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) - return bookmarks; - EnumerateRoot(rootElement, bookmarks, source); + using var jsonDocument = JsonDocument.Parse(File.ReadAllText(path)); + if (!jsonDocument.RootElement.TryGetProperty("roots", out var rootElement)) return bookmarks; - } + EnumerateRoot(rootElement, bookmarks, source); + return bookmarks; + } - private void EnumerateRoot(JsonElement rootElement, ICollection bookmarks, string source) + private void EnumerateRoot(JsonElement rootElement, ICollection bookmarks, string source) + { + foreach (var folder in rootElement.EnumerateObject()) { - foreach (var folder in rootElement.EnumerateObject()) - { - if (folder.Value.ValueKind != JsonValueKind.Object) - continue; + if (folder.Value.ValueKind != JsonValueKind.Object) + continue; - // Fix for Opera. It stores bookmarks slightly different than chrome. See PR and bug report for this change for details. - // If various exceptions start to build up here consider splitting this Loader into multiple separate ones. - if (folder.Name == "custom_root") - EnumerateRoot(folder.Value, bookmarks, source); - else - EnumerateFolderBookmark(folder.Value, bookmarks, source); + // Fix for Opera. It stores bookmarks slightly different than chrome. See PR and bug report for this change for details. + // If various exceptions start to build up here consider splitting this Loader into multiple separate ones. + if (folder.Name == "custom_root") + EnumerateRoot(folder.Value, bookmarks, source); + else + EnumerateFolderBookmark(folder.Value, bookmarks, source); + } + } + + private void EnumerateFolderBookmark(JsonElement folderElement, ICollection bookmarks, + string source) + { + if (!folderElement.TryGetProperty("children", out var childrenElement)) + return; + foreach (var subElement in childrenElement.EnumerateArray()) + { + if (subElement.TryGetProperty("type", out var type)) + { + switch (type.GetString()) + { + case "folder": + case "workspace": // Edge Workspace + EnumerateFolderBookmark(subElement, bookmarks, source); + break; + default: + bookmarks.Add(new Bookmark( + subElement.GetProperty("name").GetString(), + subElement.GetProperty("url").GetString(), + source)); + break; + } } - } - - private void EnumerateFolderBookmark(JsonElement folderElement, ICollection bookmarks, - string source) - { - if (!folderElement.TryGetProperty("children", out var childrenElement)) - return; - foreach (var subElement in childrenElement.EnumerateArray()) + else { - if (subElement.TryGetProperty("type", out var type)) - { - switch (type.GetString()) - { - case "folder": - case "workspace": // Edge Workspace - EnumerateFolderBookmark(subElement, bookmarks, source); - break; - default: - bookmarks.Add(new Bookmark( - subElement.GetProperty("name").GetString(), - subElement.GetProperty("url").GetString(), - source)); - break; - } - } - else - { - Log.Error( - $"ChromiumBookmarkLoader: EnumerateFolderBookmark: type property not found for {subElement.GetString()}"); - } + Log.Error( + $"ChromiumBookmarkLoader: EnumerateFolderBookmark: type property not found for {subElement.GetString()}"); } } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs index d08c05b6b..3468015eb 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Commands/BookmarkLoader.cs @@ -4,56 +4,55 @@ using Flow.Launcher.Infrastructure; using Flow.Launcher.Plugin.BrowserBookmark.Models; using Flow.Launcher.Plugin.SharedModels; -namespace Flow.Launcher.Plugin.BrowserBookmark.Commands +namespace Flow.Launcher.Plugin.BrowserBookmark.Commands; + +internal static class BookmarkLoader { - internal static class BookmarkLoader + internal static MatchResult MatchProgram(Bookmark bookmark, string queryString) { - internal static MatchResult MatchProgram(Bookmark bookmark, string queryString) - { - var match = StringMatcher.FuzzySearch(queryString, bookmark.Name); - if (match.IsSearchPrecisionScoreMet()) - return match; + var match = StringMatcher.FuzzySearch(queryString, bookmark.Name); + if (match.IsSearchPrecisionScoreMet()) + return match; - return StringMatcher.FuzzySearch(queryString, bookmark.Url); + return StringMatcher.FuzzySearch(queryString, bookmark.Url); + } + + internal static List LoadAllBookmarks(Settings setting) + { + var allBookmarks = new List(); + + if (setting.LoadChromeBookmark) + { + // Add Chrome bookmarks + var chromeBookmarks = new ChromeBookmarkLoader(); + allBookmarks.AddRange(chromeBookmarks.GetBookmarks()); } - internal static List LoadAllBookmarks(Settings setting) + if (setting.LoadFirefoxBookmark) { - var allBookmarks = new List(); - - if (setting.LoadChromeBookmark) - { - // Add Chrome bookmarks - var chromeBookmarks = new ChromeBookmarkLoader(); - allBookmarks.AddRange(chromeBookmarks.GetBookmarks()); - } - - if (setting.LoadFirefoxBookmark) - { - // Add Firefox bookmarks - var mozBookmarks = new FirefoxBookmarkLoader(); - allBookmarks.AddRange(mozBookmarks.GetBookmarks()); - } - - if (setting.LoadEdgeBookmark) - { - // Add Edge (Chromium) bookmarks - var edgeBookmarks = new EdgeBookmarkLoader(); - allBookmarks.AddRange(edgeBookmarks.GetBookmarks()); - } - - foreach (var browser in setting.CustomChromiumBrowsers) - { - IBookmarkLoader loader = browser.BrowserType switch - { - BrowserType.Chromium => new CustomChromiumBookmarkLoader(browser), - BrowserType.Firefox => new CustomFirefoxBookmarkLoader(browser), - _ => new CustomChromiumBookmarkLoader(browser), - }; - allBookmarks.AddRange(loader.GetBookmarks()); - } - - return allBookmarks.Distinct().ToList(); + // Add Firefox bookmarks + var mozBookmarks = new FirefoxBookmarkLoader(); + allBookmarks.AddRange(mozBookmarks.GetBookmarks()); } + + if (setting.LoadEdgeBookmark) + { + // Add Edge (Chromium) bookmarks + var edgeBookmarks = new EdgeBookmarkLoader(); + allBookmarks.AddRange(edgeBookmarks.GetBookmarks()); + } + + foreach (var browser in setting.CustomChromiumBrowsers) + { + IBookmarkLoader loader = browser.BrowserType switch + { + BrowserType.Chromium => new CustomChromiumBookmarkLoader(browser), + BrowserType.Firefox => new CustomFirefoxBookmarkLoader(browser), + _ => new CustomChromiumBookmarkLoader(browser), + }; + allBookmarks.AddRange(loader.GetBookmarks()); + } + + return allBookmarks.Distinct().ToList(); } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs index fa98f4d7c..005c83992 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomChromiumBookmarkLoader.cs @@ -1,19 +1,18 @@ using Flow.Launcher.Plugin.BrowserBookmark.Models; using System.Collections.Generic; -namespace Flow.Launcher.Plugin.BrowserBookmark -{ - public class CustomChromiumBookmarkLoader : ChromiumBookmarkLoader - { - public CustomChromiumBookmarkLoader(CustomBrowser browser) - { - BrowserName = browser.Name; - BrowserDataPath = browser.DataDirectoryPath; - } - public string BrowserDataPath { get; init; } - public string BookmarkFilePath { get; init; } - public string BrowserName { get; init; } +namespace Flow.Launcher.Plugin.BrowserBookmark; - public override List GetBookmarks() => BrowserDataPath != null ? LoadBookmarks(BrowserDataPath, BrowserName) : LoadBookmarksFromFile(BookmarkFilePath, BrowserName); +public class CustomChromiumBookmarkLoader : ChromiumBookmarkLoader +{ + public CustomChromiumBookmarkLoader(CustomBrowser browser) + { + BrowserName = browser.Name; + BrowserDataPath = browser.DataDirectoryPath; } -} \ No newline at end of file + public string BrowserDataPath { get; init; } + public string BookmarkFilePath { get; init; } + public string BrowserName { get; init; } + + public override List GetBookmarks() => BrowserDataPath != null ? LoadBookmarks(BrowserDataPath, BrowserName) : LoadBookmarksFromFile(BookmarkFilePath, BrowserName); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs index 82bdc29f5..d0bb7b0cc 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/CustomFirefoxBookmarkLoader.cs @@ -2,26 +2,25 @@ using System.IO; using Flow.Launcher.Plugin.BrowserBookmark.Models; -namespace Flow.Launcher.Plugin.BrowserBookmark -{ - public class CustomFirefoxBookmarkLoader : FirefoxBookmarkLoaderBase - { - public CustomFirefoxBookmarkLoader(CustomBrowser browser) - { - BrowserName = browser.Name; - BrowserDataPath = browser.DataDirectoryPath; - } - - /// - /// Path to places.sqlite - /// - public string BrowserDataPath { get; init; } - - public string BrowserName { get; init; } +namespace Flow.Launcher.Plugin.BrowserBookmark; - public override List GetBookmarks() - { - return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite")); - } +public class CustomFirefoxBookmarkLoader : FirefoxBookmarkLoaderBase +{ + public CustomFirefoxBookmarkLoader(CustomBrowser browser) + { + BrowserName = browser.Name; + BrowserDataPath = browser.DataDirectoryPath; + } + + /// + /// Path to places.sqlite + /// + public string BrowserDataPath { get; init; } + + public string BrowserName { get; init; } + + public override List GetBookmarks() + { + return GetBookmarksFromPath(Path.Combine(BrowserDataPath, "places.sqlite")); } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs index 79190f0ef..40123b022 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/EdgeBookmarkLoader.cs @@ -3,21 +3,20 @@ using System; using System.Collections.Generic; using System.IO; -namespace Flow.Launcher.Plugin.BrowserBookmark -{ - public class EdgeBookmarkLoader : ChromiumBookmarkLoader - { - private List LoadEdgeBookmarks() - { - var bookmarks = new List(); - var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev")); - bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary")); +namespace Flow.Launcher.Plugin.BrowserBookmark; - return bookmarks; - } - - public override List GetBookmarks() => LoadEdgeBookmarks(); +public class EdgeBookmarkLoader : ChromiumBookmarkLoader +{ + private List LoadEdgeBookmarks() + { + var bookmarks = new List(); + var platformPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge\User Data"), "Microsoft Edge")); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge Dev\User Data"), "Microsoft Edge Dev")); + bookmarks.AddRange(LoadBookmarks(Path.Combine(platformPath, @"Microsoft\Edge SxS\User Data"), "Microsoft Edge Canary")); + + return bookmarks; } -} \ No newline at end of file + + public override List GetBookmarks() => LoadEdgeBookmarks(); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs index 3d061e758..35ad32fb3 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/FirefoxBookmarkLoader.cs @@ -5,140 +5,133 @@ using System.Collections.Generic; using System.IO; using System.Linq; -namespace Flow.Launcher.Plugin.BrowserBookmark +namespace Flow.Launcher.Plugin.BrowserBookmark; + +public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader { - public abstract class FirefoxBookmarkLoaderBase : IBookmarkLoader - { - public abstract List GetBookmarks(); + public abstract List GetBookmarks(); - private const string queryAllBookmarks = @"SELECT moz_places.url, moz_bookmarks.title - FROM moz_places - INNER JOIN moz_bookmarks ON ( + private const string QueryAllBookmarks = """ + SELECT moz_places.url, moz_bookmarks.title + FROM moz_places + INNER JOIN moz_bookmarks ON ( moz_bookmarks.fk NOT NULL AND moz_bookmarks.title NOT NULL AND moz_bookmarks.fk = moz_places.id - ) - ORDER BY moz_places.visit_count DESC - "; + ) + ORDER BY moz_places.visit_count DESC + """; - private const string dbPathFormat = "Data Source ={0}"; + private const string DbPathFormat = "Data Source ={0}"; - protected static List GetBookmarksFromPath(string placesPath) - { - // Return empty list if the places.sqlite file cannot be found - if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath)) - return new List(); + protected static List GetBookmarksFromPath(string placesPath) + { + // Return empty list if the places.sqlite file cannot be found + if (string.IsNullOrEmpty(placesPath) || !File.Exists(placesPath)) + return new List(); - var bookmarkList = new List(); + Main.RegisterBookmarkFile(placesPath); - Main.RegisterBookmarkFile(placesPath); + // create the connection string and init the connection + string dbPath = string.Format(DbPathFormat, placesPath); + using var dbConnection = new SqliteConnection(dbPath); + // Open connection to the database file and execute the query + dbConnection.Open(); + var reader = new SqliteCommand(QueryAllBookmarks, dbConnection).ExecuteReader(); - // create the connection string and init the connection - string dbPath = string.Format(dbPathFormat, placesPath); - using var dbConnection = new SqliteConnection(dbPath); - // Open connection to the database file and execute the query - dbConnection.Open(); - var reader = new SqliteCommand(queryAllBookmarks, dbConnection).ExecuteReader(); - - // return results in List format - bookmarkList = reader.Select( - x => new Bookmark(x["title"] is DBNull ? string.Empty : x["title"].ToString(), - x["url"].ToString()) - ).ToList(); + // return results in List format + return reader + .Select( + x => new Bookmark( + x["title"] is DBNull ? string.Empty : x["title"].ToString(), + x["url"].ToString() + ) + ) + .ToList(); + } +} - return bookmarkList; - } + +public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase +{ + /// + /// Searches the places.sqlite db and returns all bookmarks + /// + public override List GetBookmarks() + { + return GetBookmarksFromPath(PlacesPath); } - - public class FirefoxBookmarkLoader : FirefoxBookmarkLoaderBase + /// + /// Path to places.sqlite + /// + private string PlacesPath { - /// - /// Searches the places.sqlite db and returns all bookmarks - /// - public override List GetBookmarks() + get { - return GetBookmarksFromPath(PlacesPath); - } - - /// - /// Path to places.sqlite - /// - private string PlacesPath - { - get - { - var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox"); - var profileIni = Path.Combine(profileFolderPath, @"profiles.ini"); + var profileFolderPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Mozilla\Firefox"); + var profileIni = Path.Combine(profileFolderPath, @"profiles.ini"); - if (!File.Exists(profileIni)) - return string.Empty; + if (!File.Exists(profileIni)) + return string.Empty; - // get firefox default profile directory from profiles.ini - string ini; - using (var sReader = new StreamReader(profileIni)) - { - ini = sReader.ReadToEnd(); - } + // get firefox default profile directory from profiles.ini + using var sReader = new StreamReader(profileIni); + var ini = sReader.ReadToEnd(); - /* - Current profiles.ini structure example as of Firefox version 69.0.1 - - [Install736426B0AF4A39CB] - Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile - Locked=1 + /* + Current profiles.ini structure example as of Firefox version 69.0.1 - [Profile2] - Name=newblahprofile - IsRelative=0 - Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code. + [Install736426B0AF4A39CB] + Default=Profiles/7789f565.default-release <== this is the default profile this plugin will get the bookmarks from. When opened Firefox will load the default profile + Locked=1 - [Profile1] - Name=default - IsRelative=1 - Path=Profiles/cydum7q4.default - Default=1 + [Profile2] + Name=newblahprofile + IsRelative=0 + Path=C:\t6h2yuq8.newblahprofile <== Note this is a custom location path for the profile user can set, we need to cater for this in code. - [Profile0] - Name=default-release - IsRelative=1 - Path=Profiles/7789f565.default-release + [Profile1] + Name=default + IsRelative=1 + Path=Profiles/cydum7q4.default + Default=1 - [General] - StartWithLastProfile=1 - Version=2 - */ + [Profile0] + Name=default-release + IsRelative=1 + Path=Profiles/7789f565.default-release - var lines = ini.Split(new string[] - { - "\r\n" - }, StringSplitOptions.None).ToList(); + [General] + StartWithLastProfile=1 + Version=2 + */ + var lines = ini.Split("\r\n").ToList(); - var defaultProfileFolderNameRaw = lines.Where(x => x.Contains("Default=") && x != "Default=1").FirstOrDefault() ?? string.Empty; + var defaultProfileFolderNameRaw = lines.FirstOrDefault(x => x.Contains("Default=") && x != "Default=1") ?? string.Empty; - if (string.IsNullOrEmpty(defaultProfileFolderNameRaw)) - return string.Empty; + if (string.IsNullOrEmpty(defaultProfileFolderNameRaw)) + return string.Empty; - var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last(); + var defaultProfileFolderName = defaultProfileFolderNameRaw.Split('=').Last(); - var indexOfDefaultProfileAtttributePath = lines.IndexOf("Path=" + defaultProfileFolderName); + var indexOfDefaultProfileAttributePath = lines.IndexOf("Path=" + defaultProfileFolderName); - // Seen in the example above, the IsRelative attribute is always above the Path attribute - var relativeAttribute = lines[indexOfDefaultProfileAtttributePath - 1]; + // Seen in the example above, the IsRelative attribute is always above the Path attribute + var relativeAttribute = lines[indexOfDefaultProfileAttributePath - 1]; - return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0 - ? defaultProfileFolderName + @"\places.sqlite" - : Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite"; - } - } - } - - public static class Extensions - { - public static IEnumerable Select(this SqliteDataReader reader, Func projection) - { - while (reader.Read()) - { - yield return projection(reader); - } + return relativeAttribute == "0" // See above, the profile is located in a custom location, path is not relative, so IsRelative=0 + ? defaultProfileFolderName + @"\places.sqlite" + : Path.Combine(profileFolderPath, defaultProfileFolderName) + @"\places.sqlite"; + } + } +} + +public static class Extensions +{ + public static IEnumerable Select(this SqliteDataReader reader, Func projection) + { + while (reader.Read()) + { + yield return projection(reader); } } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj index fe118c2c3..234afe28a 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Flow.Launcher.Plugin.BrowserBookmark.csproj @@ -12,6 +12,7 @@ false false true + en @@ -35,6 +36,44 @@ false + + + + + + + + PreserveNewest @@ -59,4 +98,4 @@ - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs index 2c48cfd55..8a9727352 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/IBookmarkLoader.cs @@ -1,10 +1,9 @@ using Flow.Launcher.Plugin.BrowserBookmark.Models; using System.Collections.Generic; -namespace Flow.Launcher.Plugin.BrowserBookmark +namespace Flow.Launcher.Plugin.BrowserBookmark; + +public interface IBookmarkLoader { - public interface IBookmarkLoader - { - public List GetBookmarks(); - } -} \ No newline at end of file + public List GetBookmarks(); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/vi.xaml new file mode 100644 index 000000000..ddc10950e --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Languages/vi.xaml @@ -0,0 +1,28 @@ + + + + + Dấu trang trình duyệt + Tìm kiếm dấu trang trình duyệt của bạn + + + Dữ liệu đánh dấu + Mở dấu trang trong: + Cửa sổ mới + Thêm Tab mới + Đặt trình duyệt từ đường dẫn: + Chọn + Sao chép url + Sao chép url của dấu trang vào clipboard + Tải trình duyệt từ: + Tên trình duyệt + Đường dẫn thư mục dữ liệu + Thêm + Sửa + Xóa + Duyệt + Khác + Công cụ trình duyệt + Nếu bạn không sử dụng Chrome, Firefox hoặc Edge hoặc bạn đang sử dụng phiên bản di động của chúng, bạn cần thêm thư mục dữ liệu dấu trang và chọn đúng công cụ trình duyệt để plugin này hoạt động. + Ví dụ: Engine của Brave là Chrome; và vị trí dữ liệu dấu trang mặc định của nó là: "%LOCALAPPDATA%\BraveSoftware\Brave-Browser\UserData". Đối với công cụ Firefox, thư mục dấu trang là thư mục dữ liệu người dùng chứa tệp địa điểm.sqlite. + diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs index a13d6c929..a48d70f2d 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Main.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Windows; using System.Windows.Controls; using Flow.Launcher.Infrastructure.Logger; using Flow.Launcher.Plugin.BrowserBookmark.Commands; @@ -12,233 +11,234 @@ using System.Threading.Channels; using System.Threading.Tasks; using System.Threading; -namespace Flow.Launcher.Plugin.BrowserBookmark +namespace Flow.Launcher.Plugin.BrowserBookmark; + +public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable { - public class Main : ISettingProvider, IPlugin, IReloadable, IPluginI18n, IContextMenu, IDisposable + private static PluginInitContext _context; + + private static List _cachedBookmarks = new List(); + + private static Settings _settings; + + private static bool _initialized = false; + + public void Init(PluginInitContext context) { - private static PluginInitContext context; + _context = context; - private static List cachedBookmarks = new List(); + _settings = context.API.LoadSettingJsonStorage(); - private static Settings _settings; + LoadBookmarksIfEnabled(); + } - private static bool initialized = false; - - public void Init(PluginInitContext context) + private static void LoadBookmarksIfEnabled() + { + if (_context.CurrentPluginMetadata.Disabled) { - Main.context = context; + // Don't load or monitor files if disabled + return; + } - _settings = context.API.LoadSettingJsonStorage(); + _cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings); + _ = MonitorRefreshQueueAsync(); + _initialized = true; + } + public List Query(Query query) + { + // For when the plugin being previously disabled and is now re-enabled + if (!_initialized) + { LoadBookmarksIfEnabled(); } - private static void LoadBookmarksIfEnabled() + string param = query.Search.TrimStart(); + + // Should top results be returned? (true if no search parameters have been passed) + var topResults = string.IsNullOrEmpty(param); + + + if (!topResults) { - if (context.CurrentPluginMetadata.Disabled) - { - // Don't load or monitor files if disabled - return; - } - - cachedBookmarks = BookmarkLoader.LoadAllBookmarks(_settings); - _ = MonitorRefreshQueueAsync(); - initialized = true; - } - - public List Query(Query query) - { - // For when the plugin being previously disabled and is now renabled - if (!initialized) - { - LoadBookmarksIfEnabled(); - } - - string param = query.Search.TrimStart(); - - // Should top results be returned? (true if no search parameters have been passed) - var topResults = string.IsNullOrEmpty(param); - - - if (!topResults) - { - // Since we mixed chrome and firefox bookmarks, we should order them again - var returnList = cachedBookmarks.Select(c => new Result() - { - Title = c.Name, - SubTitle = c.Url, - IcoPath = @"Images\bookmark.png", - Score = BookmarkLoader.MatchProgram(c, param).Score, - Action = _ => + // Since we mixed chrome and firefox bookmarks, we should order them again + return _cachedBookmarks + .Select( + c => new Result { - context.API.OpenUrl(c.Url); - - return true; - }, - ContextData = new BookmarkAttributes - { - Url = c.Url - } - }).Where(r => r.Score > 0); - return returnList.ToList(); - } - else - { - return cachedBookmarks.Select(c => new Result() - { - Title = c.Name, - SubTitle = c.Url, - IcoPath = @"Images\bookmark.png", - Score = 5, - Action = _ => - { - context.API.OpenUrl(c.Url); - return true; - }, - ContextData = new BookmarkAttributes - { - Url = c.Url - } - }).ToList(); - } - } - - - private static Channel refreshQueue = Channel.CreateBounded(1); - - private static SemaphoreSlim fileMonitorSemaphore = new(1, 1); - - private static async Task MonitorRefreshQueueAsync() - { - if (fileMonitorSemaphore.CurrentCount < 1) - { - return; - } - await fileMonitorSemaphore.WaitAsync(); - var reader = refreshQueue.Reader; - while (await reader.WaitToReadAsync()) - { - if (reader.TryRead(out _)) - { - ReloadAllBookmarks(false); - } - } - fileMonitorSemaphore.Release(); - } - - private static readonly List Watchers = new(); - - internal static void RegisterBookmarkFile(string path) - { - var directory = Path.GetDirectoryName(path); - if (!Directory.Exists(directory) || !File.Exists(path)) - { - return; - } - if (Watchers.Any(x => x.Path.Equals(directory, StringComparison.OrdinalIgnoreCase))) - { - return; - } - - var watcher = new FileSystemWatcher(directory!); - watcher.Filter = Path.GetFileName(path); - - watcher.NotifyFilter = NotifyFilters.FileName | - NotifyFilters.LastWrite | - NotifyFilters.Size; - - watcher.Changed += static (_, _) => - { - refreshQueue.Writer.TryWrite(default); - }; - - watcher.Renamed += static (_, _) => - { - refreshQueue.Writer.TryWrite(default); - }; - - watcher.EnableRaisingEvents = true; - - Watchers.Add(watcher); - } - - public void ReloadData() - { - ReloadAllBookmarks(); - } - - public static void ReloadAllBookmarks(bool disposeFileWatchers = true) - { - cachedBookmarks.Clear(); - if (disposeFileWatchers) - DisposeFileWatchers(); - LoadBookmarksIfEnabled(); - } - - public string GetTranslatedPluginTitle() - { - return context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_name"); - } - - public string GetTranslatedPluginDescription() - { - return context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_description"); - } - - public Control CreateSettingPanel() - { - return new SettingsControl(_settings); - } - - public List LoadContextMenus(Result selectedResult) - { - return new List() - { - new Result - { - Title = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"), - SubTitle = context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"), - Action = _ => - { - try + Title = c.Name, + SubTitle = c.Url, + IcoPath = @"Images\bookmark.png", + Score = BookmarkLoader.MatchProgram(c, param).Score, + Action = _ => { - context.API.CopyToClipboard(((BookmarkAttributes)selectedResult.ContextData).Url); + _context.API.OpenUrl(c.Url); return true; - } - catch (Exception e) + }, + ContextData = new BookmarkAttributes { Url = c.Url } + } + ) + .Where(r => r.Score > 0) + .ToList(); + } + else + { + return _cachedBookmarks + .Select( + c => new Result + { + Title = c.Name, + SubTitle = c.Url, + IcoPath = @"Images\bookmark.png", + Score = 5, + Action = _ => { - var message = "Failed to set url in clipboard"; - Log.Exception("Main", message, e, "LoadContextMenus"); - - context.API.ShowMsg(message); - - return false; - } - }, - IcoPath = "Images\\copylink.png", - Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8") - } - }; - } - - internal class BookmarkAttributes - { - internal string Url { get; set; } - } - - public void Dispose() - { - DisposeFileWatchers(); - } - - private static void DisposeFileWatchers() - { - foreach (var watcher in Watchers) - { - watcher.Dispose(); - } - Watchers.Clear(); + _context.API.OpenUrl(c.Url); + return true; + }, + ContextData = new BookmarkAttributes { Url = c.Url } + } + ) + .ToList(); } } + + + private static Channel _refreshQueue = Channel.CreateBounded(1); + + private static SemaphoreSlim _fileMonitorSemaphore = new(1, 1); + + private static async Task MonitorRefreshQueueAsync() + { + if (_fileMonitorSemaphore.CurrentCount < 1) + { + return; + } + await _fileMonitorSemaphore.WaitAsync(); + var reader = _refreshQueue.Reader; + while (await reader.WaitToReadAsync()) + { + if (reader.TryRead(out _)) + { + ReloadAllBookmarks(false); + } + } + _fileMonitorSemaphore.Release(); + } + + private static readonly List Watchers = new(); + + internal static void RegisterBookmarkFile(string path) + { + var directory = Path.GetDirectoryName(path); + if (!Directory.Exists(directory) || !File.Exists(path)) + { + return; + } + if (Watchers.Any(x => x.Path.Equals(directory, StringComparison.OrdinalIgnoreCase))) + { + return; + } + + var watcher = new FileSystemWatcher(directory!); + watcher.Filter = Path.GetFileName(path); + + watcher.NotifyFilter = NotifyFilters.FileName | + NotifyFilters.LastWrite | + NotifyFilters.Size; + + watcher.Changed += static (_, _) => + { + _refreshQueue.Writer.TryWrite(default); + }; + + watcher.Renamed += static (_, _) => + { + _refreshQueue.Writer.TryWrite(default); + }; + + watcher.EnableRaisingEvents = true; + + Watchers.Add(watcher); + } + + public void ReloadData() + { + ReloadAllBookmarks(); + } + + public static void ReloadAllBookmarks(bool disposeFileWatchers = true) + { + _cachedBookmarks.Clear(); + if (disposeFileWatchers) + DisposeFileWatchers(); + LoadBookmarksIfEnabled(); + } + + public string GetTranslatedPluginTitle() + { + return _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_name"); + } + + public string GetTranslatedPluginDescription() + { + return _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_plugin_description"); + } + + public Control CreateSettingPanel() + { + return new SettingsControl(_settings); + } + + public List LoadContextMenus(Result selectedResult) + { + return new List() + { + new Result + { + Title = _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_title"), + SubTitle = _context.API.GetTranslation("flowlauncher_plugin_browserbookmark_copyurl_subtitle"), + Action = _ => + { + try + { + _context.API.CopyToClipboard(((BookmarkAttributes)selectedResult.ContextData).Url); + + return true; + } + catch (Exception e) + { + var message = "Failed to set url in clipboard"; + Log.Exception("Main", message, e, "LoadContextMenus"); + + _context.API.ShowMsg(message); + + return false; + } + }, + IcoPath = @"Images\copylink.png", + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue8c8") + } + }; + } + + internal class BookmarkAttributes + { + internal string Url { get; set; } + } + + public void Dispose() + { + DisposeFileWatchers(); + } + + private static void DisposeFileWatchers() + { + foreach (var watcher in Watchers) + { + watcher.Dispose(); + } + Watchers.Clear(); + } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs index c2fa9d977..c738da389 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Bookmark.cs @@ -1,22 +1,21 @@ using System.Collections.Generic; -namespace Flow.Launcher.Plugin.BrowserBookmark.Models +namespace Flow.Launcher.Plugin.BrowserBookmark.Models; + +// Source may be important in the future +public record Bookmark(string Name, string Url, string Source = "") { - // Source may be important in the future - public record Bookmark(string Name, string Url, string Source = "") + public override int GetHashCode() { - public override int GetHashCode() - { - var hashName = Name?.GetHashCode() ?? 0; - var hashUrl = Url?.GetHashCode() ?? 0; - return hashName ^ hashUrl; - } - - public virtual bool Equals(Bookmark other) - { - return other != null && Name == other.Name && Url == other.Url; - } - - public List CustomBrowsers { get; set; }= new(); + var hashName = Name?.GetHashCode() ?? 0; + var hashUrl = Url?.GetHashCode() ?? 0; + return hashName ^ hashUrl; } -} \ No newline at end of file + + public virtual bool Equals(Bookmark other) + { + return other != null && Name == other.Name && Url == other.Url; + } + + public List CustomBrowsers { get; set; } = new(); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs index 69bb56e48..74e0f299a 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/CustomBrowser.cs @@ -1,45 +1,44 @@ -namespace Flow.Launcher.Plugin.BrowserBookmark.Models -{ - public class CustomBrowser : BaseModel - { - private string _name; - private string _dataDirectoryPath; - private BrowserType browserType = BrowserType.Chromium; +namespace Flow.Launcher.Plugin.BrowserBookmark.Models; - public string Name +public class CustomBrowser : BaseModel +{ + private string _name; + private string _dataDirectoryPath; + private BrowserType _browserType = BrowserType.Chromium; + + public string Name + { + get => _name; + set { - get => _name; - set - { - _name = value; - OnPropertyChanged(nameof(Name)); - } - } - - public string DataDirectoryPath - { - get => _dataDirectoryPath; - set - { - _dataDirectoryPath = value; - OnPropertyChanged(nameof(DataDirectoryPath)); - } - } - - public BrowserType BrowserType - { - get => browserType; - set - { - browserType = value; - OnPropertyChanged(nameof(BrowserType)); - } + _name = value; + OnPropertyChanged(); } } - public enum BrowserType + public string DataDirectoryPath { - Chromium, - Firefox, + get => _dataDirectoryPath; + set + { + _dataDirectoryPath = value; + OnPropertyChanged(); + } + } + + public BrowserType BrowserType + { + get => _browserType; + set + { + _browserType = value; + OnPropertyChanged(); + } } } + +public enum BrowserType +{ + Chromium, + Firefox, +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs index dc1016b4e..86532d275 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Models/Settings.cs @@ -1,17 +1,16 @@ using System.Collections.ObjectModel; -namespace Flow.Launcher.Plugin.BrowserBookmark.Models +namespace Flow.Launcher.Plugin.BrowserBookmark.Models; + +public class Settings : BaseModel { - public class Settings : BaseModel - { - public bool OpenInNewBrowserWindow { get; set; } = true; + public bool OpenInNewBrowserWindow { get; set; } = true; - public string BrowserPath { get; set; } + public string BrowserPath { get; set; } - public bool LoadChromeBookmark { get; set; } = true; - public bool LoadFirefoxBookmark { get; set; } = true; - public bool LoadEdgeBookmark { get; set; } = true; + public bool LoadChromeBookmark { get; set; } = true; + public bool LoadFirefoxBookmark { get; set; } = true; + public bool LoadEdgeBookmark { get; set; } = true; - public ObservableCollection CustomChromiumBrowsers { get; set; } = new(); - } -} \ No newline at end of file + public ObservableCollection CustomChromiumBrowsers { get; set; } = new(); +} diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml index 392c9e0a7..f5017ced5 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/CustomBrowserSetting.xaml @@ -63,7 +63,6 @@ +/// Interaction logic for CustomBrowserSetting.xaml +/// +public partial class CustomBrowserSettingWindow : Window { - /// - /// Interaction logic for CustomBrowserSetting.xaml - /// - public partial class CustomBrowserSettingWindow : Window + private CustomBrowser _currentCustomBrowser; + public CustomBrowserSettingWindow(CustomBrowser browser) { - private CustomBrowser currentCustomBrowser; - public CustomBrowserSettingWindow(CustomBrowser browser) + InitializeComponent(); + _currentCustomBrowser = browser; + DataContext = new CustomBrowser { - InitializeComponent(); - currentCustomBrowser = browser; - DataContext = new CustomBrowser - { - Name = browser.Name, - DataDirectoryPath = browser.DataDirectoryPath, - BrowserType = browser.BrowserType, - }; - } - - private void ConfirmEditCustomBrowser(object sender, RoutedEventArgs e) - { - CustomBrowser editBrowser = (CustomBrowser)DataContext; - currentCustomBrowser.Name = editBrowser.Name; - currentCustomBrowser.DataDirectoryPath = editBrowser.DataDirectoryPath; - currentCustomBrowser.BrowserType = editBrowser.BrowserType; - DialogResult = true; - Close(); - } + Name = browser.Name, + DataDirectoryPath = browser.DataDirectoryPath, + BrowserType = browser.BrowserType, + }; + } - private void CancelEditCustomBrowser(object sender, RoutedEventArgs e) - { - Close(); - } + private void ConfirmEditCustomBrowser(object sender, RoutedEventArgs e) + { + CustomBrowser editBrowser = (CustomBrowser)DataContext; + _currentCustomBrowser.Name = editBrowser.Name; + _currentCustomBrowser.DataDirectoryPath = editBrowser.DataDirectoryPath; + _currentCustomBrowser.BrowserType = editBrowser.BrowserType; + DialogResult = true; + Close(); + } - private void WindowKeyDown(object sender, System.Windows.Input.KeyEventArgs e) - { - if (e.Key == Key.Enter) - { - ConfirmEditCustomBrowser(sender, e); - } - } + private void CancelEditCustomBrowser(object sender, RoutedEventArgs e) + { + Close(); + } - private void OnSelectPathClick(object sender, RoutedEventArgs e) + private void WindowKeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == Key.Enter) { - var dialog = new FolderBrowserDialog(); - dialog.ShowDialog(); - CustomBrowser editBrowser = (CustomBrowser)DataContext; - editBrowser.DataDirectoryPath = dialog.SelectedPath; + ConfirmEditCustomBrowser(sender, e); } } + + private void OnSelectPathClick(object sender, RoutedEventArgs e) + { + var dialog = new FolderBrowserDialog(); + dialog.ShowDialog(); + CustomBrowser editBrowser = (CustomBrowser)DataContext; + editBrowser.DataDirectoryPath = dialog.SelectedPath; + } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs index 2947c2cb5..4bdab89a9 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/Views/SettingsControl.xaml.cs @@ -4,119 +4,116 @@ using System.Windows.Input; using System.ComponentModel; using System.Threading.Tasks; -namespace Flow.Launcher.Plugin.BrowserBookmark.Views +namespace Flow.Launcher.Plugin.BrowserBookmark.Views; + +public partial class SettingsControl : INotifyPropertyChanged { - public partial class SettingsControl : INotifyPropertyChanged + public Settings Settings { get; } + + public CustomBrowser SelectedCustomBrowser { get; set; } + + public bool LoadChromeBookmark { - public Settings Settings { get; } - - public CustomBrowser SelectedCustomBrowser { get; set; } - - public bool LoadChromeBookmark + get => Settings.LoadChromeBookmark; + set { - get => Settings.LoadChromeBookmark; - set + Settings.LoadChromeBookmark = value; + _ = Task.Run(() => Main.ReloadAllBookmarks()); + } + } + + public bool LoadFirefoxBookmark + { + get => Settings.LoadFirefoxBookmark; + set + { + Settings.LoadFirefoxBookmark = value; + _ = Task.Run(() => Main.ReloadAllBookmarks()); + } + } + + public bool LoadEdgeBookmark + { + get => Settings.LoadEdgeBookmark; + set + { + Settings.LoadEdgeBookmark = value; + _ = Task.Run(() => Main.ReloadAllBookmarks()); + } + } + + public bool OpenInNewBrowserWindow + { + get => Settings.OpenInNewBrowserWindow; + set + { + Settings.OpenInNewBrowserWindow = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OpenInNewBrowserWindow))); + } + } + + public SettingsControl(Settings settings) + { + Settings = settings; + InitializeComponent(); + } + + public event PropertyChangedEventHandler PropertyChanged; + + private void NewCustomBrowser(object sender, RoutedEventArgs e) + { + var newBrowser = new CustomBrowser(); + var window = new CustomBrowserSettingWindow(newBrowser); + window.ShowDialog(); + if (newBrowser is not { - Settings.LoadChromeBookmark = value; - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - public bool LoadFirefoxBookmark + Name: null, + DataDirectoryPath: null + }) { - get => Settings.LoadFirefoxBookmark; - set - { - Settings.LoadFirefoxBookmark = value; - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } + Settings.CustomChromiumBrowsers.Add(newBrowser); + _ = Task.Run(() => Main.ReloadAllBookmarks()); } + } - public bool LoadEdgeBookmark + private void DeleteCustomBrowser(object sender, RoutedEventArgs e) + { + if (CustomBrowsers.SelectedItem is CustomBrowser selectedCustomBrowser) { - get => Settings.LoadEdgeBookmark; - set - { - Settings.LoadEdgeBookmark = value; - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } + Settings.CustomChromiumBrowsers.Remove(selectedCustomBrowser); + _ = Task.Run(() => Main.ReloadAllBookmarks()); } + } - public bool OpenInNewBrowserWindow + private void MouseDoubleClickOnSelectedCustomBrowser(object sender, MouseButtonEventArgs e) + { + EditSelectedCustomBrowser(); + } + + private void Others_Click(object sender, RoutedEventArgs e) + { + CustomBrowsersList.Visibility = CustomBrowsersList.Visibility switch { - get => Settings.OpenInNewBrowserWindow; - set - { - Settings.OpenInNewBrowserWindow = value; - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(OpenInNewBrowserWindow))); - } - } + Visibility.Collapsed => Visibility.Visible, + _ => Visibility.Collapsed + }; + } - public SettingsControl(Settings settings) + private void EditCustomBrowser(object sender, RoutedEventArgs e) + { + EditSelectedCustomBrowser(); + } + + private void EditSelectedCustomBrowser() + { + if (SelectedCustomBrowser is null) + return; + + var window = new CustomBrowserSettingWindow(SelectedCustomBrowser); + var result = window.ShowDialog() ?? false; + if (result) { - Settings = settings; - InitializeComponent(); - } - - public event PropertyChangedEventHandler PropertyChanged; - - private void NewCustomBrowser(object sender, RoutedEventArgs e) - { - var newBrowser = new CustomBrowser(); - var window = new CustomBrowserSettingWindow(newBrowser); - window.ShowDialog(); - if (newBrowser is not - { - Name: null, - DataDirectoryPath: null - }) - { - Settings.CustomChromiumBrowsers.Add(newBrowser); - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - private void DeleteCustomBrowser(object sender, RoutedEventArgs e) - { - if (CustomBrowsers.SelectedItem is CustomBrowser selectedCustomBrowser) - { - Settings.CustomChromiumBrowsers.Remove(selectedCustomBrowser); - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } - } - - private void MouseDoubleClickOnSelectedCustomBrowser(object sender, MouseButtonEventArgs e) - { - EditSelectedCustomBrowser(); - } - - private void Others_Click(object sender, RoutedEventArgs e) - { - - if (CustomBrowsersList.Visibility == Visibility.Collapsed) - { - CustomBrowsersList.Visibility = Visibility.Visible; - } - else - CustomBrowsersList.Visibility = Visibility.Collapsed; - } - - private void EditCustomBrowser(object sender, RoutedEventArgs e) - { - EditSelectedCustomBrowser(); - } - - private void EditSelectedCustomBrowser() - { - if (SelectedCustomBrowser is null) - return; - - var window = new CustomBrowserSettingWindow(SelectedCustomBrowser); - var result = window.ShowDialog() ?? false; - if (result) - { - _ = Task.Run(() => Main.ReloadAllBookmarks()); - } + _ = Task.Run(() => Main.ReloadAllBookmarks()); } } } diff --git a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json index 0778b7ae5..c4a24a57b 100644 --- a/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.BrowserBookmark/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser Bookmarks", "Description": "Search your browser bookmarks", "Author": "qianlifeng, Ioannis G.", - "Version": "3.1.5", + "Version": "3.2.0", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.BrowserBookmark.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs b/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs index 27bdf94ee..81a68739b 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/DecimalSeparator.cs @@ -1,7 +1,7 @@ using System.ComponentModel; using Flow.Launcher.Core.Resource; -namespace Flow.Launcher.Plugin.Caculator +namespace Flow.Launcher.Plugin.Calculator { [TypeConverter(typeof(LocalizationConverter))] public enum DecimalSeparator diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj index 415f852f4..69c03b877 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Flow.Launcher.Plugin.Calculator.csproj @@ -5,8 +5,8 @@ net7.0-windows {59BD9891-3837-438A-958D-ADC7F91F6F7E} Properties - Flow.Launcher.Plugin.Caculator - Flow.Launcher.Plugin.Caculator + Flow.Launcher.Plugin.Calculator + Flow.Launcher.Plugin.Calculator true true false @@ -18,7 +18,7 @@ true portable false - ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Caculator\ + ..\..\Output\Debug\Plugins\Flow.Launcher.Plugin.Calculator\ DEBUG;TRACE prompt 4 @@ -28,7 +28,7 @@ pdbonly true - ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Caculator\ + ..\..\Output\Release\Plugins\Flow.Launcher.Plugin.Calculator\ TRACE prompt 4 diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/vi.xaml new file mode 100644 index 000000000..82ebbbe93 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Languages/vi.xaml @@ -0,0 +1,15 @@ + + + + Máy tính + Cho phép thực hiện các phép tính toán học. (Thử 5*3-2 trong Flow Launcher) + Không phải là số (NaN) + Biểu thức sai hoặc không đầy đủ (Bạn có quên một số dấu ngoặc đơn không?) + Sao chép số này vào clipboard + Dấu tách thập phân + Dấu phân cách thập phân được sử dụng ở đầu ra. + Sử dụng ngôn ngữ hệ thống + Dấu phẩy (,) + dấu chấm (.) + Tối đa. chữ số thập phân + diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs index 338b5bcbe..5077e6061 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Main.cs @@ -6,10 +6,10 @@ using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using Mages.Core; -using Flow.Launcher.Plugin.Caculator.ViewModels; -using Flow.Launcher.Plugin.Caculator.Views; +using Flow.Launcher.Plugin.Calculator.ViewModels; +using Flow.Launcher.Plugin.Calculator.Views; -namespace Flow.Launcher.Plugin.Caculator +namespace Flow.Launcher.Plugin.Calculator { public class Main : IPlugin, IPluginI18n, ISettingProvider { @@ -62,7 +62,7 @@ namespace Flow.Launcher.Plugin.Caculator switch (_settings.DecimalSeparator) { case DecimalSeparator.Comma: - case DecimalSeparator.UseSystemLocale when CultureInfo.DefaultThreadCurrentCulture.NumberFormat.NumberDecimalSeparator == ",": + case DecimalSeparator.UseSystemLocale when CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator == ",": expression = query.Search.Replace(",", "."); break; default: @@ -158,7 +158,7 @@ namespace Flow.Launcher.Plugin.Caculator private string GetDecimalSeparator() { - string systemDecimalSeperator = CultureInfo.DefaultThreadCurrentCulture.NumberFormat.NumberDecimalSeparator; + string systemDecimalSeperator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; switch (_settings.DecimalSeparator) { case DecimalSeparator.UseSystemLocale: return systemDecimalSeperator; diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/NumberTranslator.cs b/Plugins/Flow.Launcher.Plugin.Calculator/NumberTranslator.cs index ed4aed75b..4eacb9d34 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/NumberTranslator.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/NumberTranslator.cs @@ -2,7 +2,7 @@ using System.Text; using System.Text.RegularExpressions; -namespace Flow.Launcher.Plugin.Caculator +namespace Flow.Launcher.Plugin.Calculator { /// /// Tries to convert all numbers in a text from one culture format to another. diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs index 615514873..8354863b8 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Settings.cs @@ -1,5 +1,5 @@  -namespace Flow.Launcher.Plugin.Caculator +namespace Flow.Launcher.Plugin.Calculator { public class Settings { diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs index afe4d1c0c..09f745669 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Calculator/ViewModels/SettingsViewModel.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Linq; -namespace Flow.Launcher.Plugin.Caculator.ViewModels +namespace Flow.Launcher.Plugin.Calculator.ViewModels { public class SettingsViewModel : BaseModel { diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml index 9fd4bb17c..d6237c6da 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Calculator/Views/CalculatorSettings.xaml @@ -1,13 +1,13 @@ /// Interaction logic for CalculatorSettings.xaml diff --git a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json index a77475460..6abc41668 100644 --- a/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Calculator/plugin.json @@ -4,9 +4,9 @@ "Name": "Calculator", "Description": "Provide mathematical calculations.(Try 5*3-2 in Flow Launcher)", "Author": "cxfksword", - "Version": "3.1.0", + "Version": "3.1.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", - "ExecuteFileName": "Flow.Launcher.Plugin.Caculator.dll", + "ExecuteFileName": "Flow.Launcher.Plugin.Calculator.dll", "IcoPath": "Images\\calculator.png" } \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs index 2297e5f96..43f2b885e 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ContextMenu.cs @@ -222,34 +222,7 @@ namespace Flow.Launcher.Plugin.Explorer if (record.Type is ResultType.Volume) return false; - var screenWithMouseCursor = System.Windows.Forms.Screen.FromPoint(System.Windows.Forms.Cursor.Position); - var xOfScreenCenter = screenWithMouseCursor.WorkingArea.Left + screenWithMouseCursor.WorkingArea.Width / 2; - var yOfScreenCenter = screenWithMouseCursor.WorkingArea.Top + screenWithMouseCursor.WorkingArea.Height / 2; - var showPosition = new System.Drawing.Point(xOfScreenCenter, yOfScreenCenter); - - switch (record.Type) - { - case ResultType.File: - { - var fileInfos = new FileInfo[] - { - new(record.FullPath) - }; - - new Peter.ShellContextMenu().ShowContextMenu(fileInfos, showPosition); - break; - } - case ResultType.Folder: - { - var directoryInfos = new DirectoryInfo[] - { - new(record.FullPath) - }; - - new Peter.ShellContextMenu().ShowContextMenu(directoryInfos, showPosition); - break; - } - } + ResultManager.ShowNativeContextMenu(record.FullPath, record.Type); return false; }, @@ -276,7 +249,8 @@ namespace Flow.Launcher.Plugin.Explorer return true; }, - IcoPath = Constants.DifferentUserIconImagePath + IcoPath = Constants.DifferentUserIconImagePath, + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue748"), }); } @@ -403,7 +377,8 @@ namespace Flow.Launcher.Plugin.Explorer return false; }, - IcoPath = Constants.ExcludeFromIndexImagePath + IcoPath = Constants.ExcludeFromIndexImagePath, + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uf140"), }; } @@ -435,7 +410,8 @@ namespace Flow.Launcher.Plugin.Explorer return false; } }, - IcoPath = Constants.IndexingOptionsIconImagePath + IcoPath = Constants.IndexingOptionsIconImagePath, + Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\ue773"), }; } diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png b/Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png index 552b2c5bd..25da8e49c 100644 Binary files a/Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png and b/Plugins/Flow.Launcher.Plugin.Explorer/Images/explorer.png differ diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml index f2491d928..cd4a1dc84 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/en.xaml @@ -29,6 +29,12 @@ Customise Action Keywords Quick Access Links Everything Setting + Preview Panel + Size + Date Created + Date Modified + Display File Info + Date and time format Sort Option: Everything Path: Launch Hidden @@ -105,10 +111,12 @@ Open With Select a program to open with - + {0} free of {1} Open in Default File Manager - Use '>' to search in this directory, '*' to search for file extensions or '>*' to combine both searches. + + Use '>' to search in this directory, '*' to search for file extensions or '>*' to combine both searches. + Failed to load Everything SDK @@ -133,7 +141,7 @@ Warning: This is not a Fast Sort option, searches may be slow Search Full Path - + Click to launch or install Everything Everything Installation Installing Everything service. Please wait... diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/it.xaml index f69f15868..32159af96 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/it.xaml @@ -100,8 +100,8 @@ Rimuovi da Accesso Rapido Rimuovi elemento corrente dall'Accesso Rapido Mostra Menu Contestuale di Windows - Open With - Select a program to open with + Apri Con + Seleziona un programma con cui aprire {0} disponibili su {1} diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ru.xaml index cb7f2cda5..ccdba5da9 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/ru.xaml @@ -15,9 +15,9 @@ To fix this, start the Windows Search service. Select here to remove this warning The warning message has been switched off. As an alternative for searching files and folders, would you like to install Everything plugin?{0}{0}Select 'Yes' to install Everything plugin, or 'No' to return Explorer Alternative - Error occurred during search: {0} - Could not open folder - Could not open file + При поиске произошла ошибка: {0} + Не удалось открыть папку + Не удалось открыть файл Удалить diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/vi.xaml new file mode 100644 index 000000000..ee69ba9a3 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Languages/vi.xaml @@ -0,0 +1,145 @@ + + + + + Vui lòng lựa chọn trước + Hãy chọn một thư mục. + Bạn có chắc chắn muốn xóa {0} không? + Bạn có chắc là muốn xóa vĩnh viễn các ảnh này? + Bạn có chắc là muốn xóa vĩnh viễn các ảnh này? + Xóa thành công + Thành công trong việc xóa + Việc chỉ định từ khóa hành động chung có thể mang lại quá nhiều kết quả trong quá trình tìm kiếm. Vui lòng chọn từ khóa hành động cụ thể + Không thể đặt Truy cập nhanh thành từ khóa hành động chung khi được bật. Vui lòng chọn từ khóa hành động cụ thể + Dịch vụ cần thiết cho Windows Index Search dường như không chạy + Để khắc phục điều này, hãy khởi động dịch vụ Windows Search. Chọn vào đây để xóa cảnh báo này + Thông báo cảnh báo đã bị tắt. Là một giải pháp thay thế cho việc tìm kiếm tệp và thư mục, bạn có muốn cài đặt plugin Mọi thứ không?{0}{0}Chọn 'Có' để cài đặt plugin Mọi thứ hoặc 'Không' để quay lại + Nhà thám hiểm thay thế + Đã xảy ra lỗi trong quá trình tìm kiếm: {0} + Không thể mở thư mục + Không thể mở file + + + Xóa + Sửa + Thêm + Cài đặt chung + Tùy chỉnh từ khóa hành động + Liên kết truy cập nhanh + Cài đặt mọi thứ + Tùy Chọn Sắp Xếp + Đường dẫn mọi thứ: + Khởi chạy ẩn + Đường dẫn soạn thảo + Đường dẫn Shell + Đường dẫn bị loại trừ tìm kiếm chỉ mục + Sử dụng vị trí của kết quả tìm kiếm làm thư mục làm việc của tệp thực thi + Chạy với tư cách quản trị viên / mở thư mục trong trình quản lý tệp tiêu chuẩn + Sử dụng Tìm kiếm Chỉ mục để Tìm kiếm Đường dẫn + Tùy chọn lập chỉ mục + Tìm kiếm trên web với sự hỗ trợ của công cụ tìm kiếm khác + Tìm kiếm đường dẫn: + Tìm kiếm nội dung tệp: + Tìm kiếm chỉ mục: + Truy cập nhanh + Từ hành động hiện tại + Xong + Đã bật + Khi bị tắt, Flow sẽ không thực thi tùy chọn tìm kiếm này và sẽ hoàn nguyên về '*' để giải phóng từ khóa hành động + Tất cả mọi thứ + Windows Index + Direct Enumeration + File Editor Path + Folder Editor Path + + Content Search Engine + Directory Recursive Search Engine + Index Search Engine + Mở tùy chọn lập chỉ mục Windows + + + Thư mục + Find and manage files and folders via Windows Search or Everything + + + Ctrl + Enter to open the directory + Ctrl + Enter to open the containing folder + + + Copy đường dẫn + Copy path of current item to clipboard + Sao chép + Copy current file to clipboard + Copy current folder to clipboard + Xóa + Permanently delete current file + Permanently delete current folder + Đường dẫn: + Xóa đã chọn + Xóa lựa chọn đã chọn + Chạy phần đã chọn bằng tài khoản người dùng khác + Mở thư mục chứa + Open the location that contains current item + Mở bằng trình chỉnh sửa: + Failed to open file at {0} with Editor {1} at {2} + Open With Shell: + Failed to open folder {0} with Shell {1} at {2} + Loại trừ các thư mục hiện tại và thư mục con khỏi Tìm kiếm chỉ mục + Bị loại trừ khỏi Tìm kiếm chỉ mục + Mở tùy chọn lập chỉ mục Windows + Quản lý các tập tin và thư mục được lập chỉ mục + Quản lý các tập tin và thư mục được lập chỉ mục + Thêm vào Bảng truy cập nhanh + Thêm {0} hiện tại vào Truy cập nhanh + Đã thêm thành công + Đã thêm thành công vào Truy cập nhanh + Đã được gỡ bỏ thành công + Đã xóa thành công khỏi Truy cập nhanh + Thêm vào Truy cập nhanh để có thể mở nó bằng từ khóa hành động Kích hoạt tìm kiếm của Explorer + Loại bỏ khỏi bảng truy cập nhanh + Loại bỏ khỏi bảng truy cập nhanh + Xóa {0} hiện tại khỏi Truy cập nhanh + Show Windows Context Menu + Mở bằng + Select a program to open with + + + {0} phần {1} + Trình quản lý tệp mặc định + Use '>' to search in this directory, '*' to search for file extensions or '>*' to combine both searches. + + + Failed to load Everything SDK + Warning: Everything service is not running + Error while querying Everything + Sắp xếp theo + Tên + Đường dẫn + Kích thước + Mở rộng + Loại tên + Ngày Tạo + ngày sửa đổi + Thuộc tính + File List FileName + Số lần chạy + Date Recently Changed + Ngày truy cập + Date Run + + + Warning: This is not a Fast Sort option, searches may be slow + + Search Full Path + + Click to launch or install Everything + Everything Installation + Installing Everything service. Please wait... + Successfully installed Everything service + Failed to automatically install Everything service. Please manually install it from https://www.voidtools.com + Click here to start it + Không thể tìm thấy bản cài đặt Mọi thứ, bạn có muốn chọn vị trí theo cách thủ công không?{0}{0}Nhấp vào không và Mọi thứ sẽ được cài đặt tự động cho bạn + Do you want to enable content search for Everything? + It can be very slow without index (which is only supported in Everything v1.5+) + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs index a87f766a1..02588086f 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Search/ResultManager.cs @@ -8,11 +8,16 @@ using System.Threading.Tasks; using System.Windows; using Flow.Launcher.Plugin.Explorer.Search.Everything; using System.Windows.Input; +using Path = System.IO.Path; +using System.Windows.Controls; +using Flow.Launcher.Plugin.Explorer.Views; +using Peter; namespace Flow.Launcher.Plugin.Explorer.Search { public static class ResultManager { + private static readonly string[] SizeUnits = { "B", "KB", "MB", "GB", "TB" }; private static PluginInitContext Context; private static Settings Settings { get; set; } @@ -66,6 +71,27 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } + internal static void ShowNativeContextMenu(string path, ResultType type) + { + var screenWithMouseCursor = System.Windows.Forms.Screen.FromPoint(System.Windows.Forms.Cursor.Position); + var xOfScreenCenter = screenWithMouseCursor.WorkingArea.Left + screenWithMouseCursor.WorkingArea.Width / 2; + var yOfScreenCenter = screenWithMouseCursor.WorkingArea.Top + screenWithMouseCursor.WorkingArea.Height / 2; + var showPosition = new System.Drawing.Point(xOfScreenCenter, yOfScreenCenter); + + switch (type) + { + case ResultType.File: + var fileInfo = new FileInfo[] { new(path) }; + new ShellContextMenu().ShowContextMenu(fileInfo, showPosition); + break; + + case ResultType.Folder: + var folderInfo = new System.IO.DirectoryInfo[] { new(path) }; + new ShellContextMenu().ShowContextMenu(folderInfo, showPosition); + break; + } + } + internal static Result CreateFolderResult(string title, string subtitle, string path, Query query, int score = 0, bool windowsIndexed = false) { return new Result @@ -76,8 +102,17 @@ namespace Flow.Launcher.Plugin.Explorer.Search AutoCompleteText = GetAutoCompleteText(title, query, path, ResultType.Folder), TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData, CopyText = path, + Preview = new Result.PreviewInfo + { + FilePath = path, + }, Action = c => { + if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt) + { + ShowNativeContextMenu(path, ResultType.Folder); + return false; + } // open folder if (c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift)) { @@ -161,6 +196,10 @@ namespace Flow.Launcher.Plugin.Explorer.Search Score = 500, ProgressBar = progressValue, ProgressBarColor = progressBarColor, + Preview = new Result.PreviewInfo + { + FilePath = path, + }, Action = _ => { OpenFolder(path); @@ -172,36 +211,28 @@ namespace Flow.Launcher.Plugin.Explorer.Search }; } - private static string ToReadableSize(long pDrvSize, int pi) + internal static string ToReadableSize(long sizeOnDrive, int pi) { - int mok = 0; - double drvSize = pDrvSize; - string uom = "Byte"; // Unit Of Measurement + var unitIndex = 0; + double readableSize = sizeOnDrive; - while (drvSize > 1024.0) + while (readableSize > 1024.0 && unitIndex < SizeUnits.Length - 1) { - drvSize /= 1024.0; - mok++; + readableSize /= 1024.0; + unitIndex++; } - if (mok == 1) - uom = "KB"; - else if (mok == 2) - uom = " MB"; - else if (mok == 3) - uom = " GB"; - else if (mok == 4) - uom = " TB"; + var unit = SizeUnits[unitIndex] ?? ""; - var returnStr = $"{Convert.ToInt32(drvSize)}{uom}"; - if (mok != 0) + var returnStr = $"{Convert.ToInt32(readableSize)} {unit}"; + if (unitIndex != 0) { returnStr = pi switch { - 1 => $"{drvSize:F1}{uom}", - 2 => $"{drvSize:F2}{uom}", - 3 => $"{drvSize:F3}{uom}", - _ => $"{Convert.ToInt32(drvSize)}{uom}" + 1 => $"{readableSize:F1} {unit}", + 2 => $"{readableSize:F2} {unit}", + 3 => $"{readableSize:F3} {unit}", + _ => $"{Convert.ToInt32(readableSize)} {unit}" }; } @@ -222,8 +253,13 @@ namespace Flow.Launcher.Plugin.Explorer.Search IcoPath = folderPath, Score = 500, CopyText = folderPath, - Action = _ => + Action = c => { + if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt) + { + ShowNativeContextMenu(folderPath, ResultType.Folder); + return false; + } OpenFolder(folderPath); return true; }, @@ -233,24 +269,35 @@ namespace Flow.Launcher.Plugin.Explorer.Search internal static Result CreateFileResult(string filePath, Query query, int score = 0, bool windowsIndexed = false) { - Result.PreviewInfo preview = IsMedia(Path.GetExtension(filePath)) - ? new Result.PreviewInfo { IsMedia = true, PreviewImagePath = filePath, } - : Result.PreviewInfo.Default; - + bool isMedia = IsMedia(Path.GetExtension(filePath)); var title = Path.GetFileName(filePath); + + /* Preview Detail */ + var result = new Result { Title = title, SubTitle = Path.GetDirectoryName(filePath), IcoPath = filePath, - Preview = preview, + Preview = new Result.PreviewInfo + { + IsMedia = isMedia, + PreviewImagePath = isMedia ? filePath : null, + FilePath = filePath, + }, AutoCompleteText = GetAutoCompleteText(title, query, filePath, ResultType.File), TitleHighlightData = StringMatcher.FuzzySearch(query.Search, title).MatchData, Score = score, CopyText = filePath, + PreviewPanel = new Lazy(() => new PreviewPanel(Settings, filePath)), Action = c => { + if (c.SpecialKeyState.ToModifierKeys() == ModifierKeys.Alt) + { + ShowNativeContextMenu(filePath, ResultType.File); + return false; + } try { if (c.SpecialKeyState.ToModifierKeys() == (ModifierKeys.Control | ModifierKeys.Shift)) diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs index 137ba2f19..088bcf5d6 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Settings.cs @@ -55,6 +55,16 @@ namespace Flow.Launcher.Plugin.Explorer public bool WarnWindowsSearchServiceOff { get; set; } = true; + public bool ShowFileSizeInPreviewPanel { get; set; } = true; + + public bool ShowCreatedDateInPreviewPanel { get; set; } = true; + + public bool ShowModifiedDateInPreviewPanel { get; set; } = true; + + public string PreviewPanelDateFormat { get; set; } = "yyyy-MM-dd"; + + public string PreviewPanelTimeFormat { get; set; } = "HH:mm"; + private EverythingSearchManager _everythingManagerInstance; private WindowsIndexSearchManager _windowsIndexSearchManager; @@ -137,7 +147,7 @@ namespace Flow.Launcher.Plugin.Explorer ContentSearchEngine == ContentIndexSearchEngineOption.Everything; public bool EverythingSearchFullPath { get; set; } = false; - + #endregion internal enum ActionKeyword diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs index 02199fb5a..064795b43 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs +++ b/Plugins/Flow.Launcher.Plugin.Explorer/ViewModels/SettingsViewModel.cs @@ -8,6 +8,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Linq; using System.Windows; @@ -101,7 +102,107 @@ namespace Flow.Launcher.Plugin.Explorer.ViewModels #endregion + #region Preview Panel + public bool ShowFileSizeInPreviewPanel + { + get => Settings.ShowFileSizeInPreviewPanel; + set + { + Settings.ShowFileSizeInPreviewPanel = value; + OnPropertyChanged(); + } + } + + public bool ShowCreatedDateInPreviewPanel + { + get => Settings.ShowCreatedDateInPreviewPanel; + set + { + Settings.ShowCreatedDateInPreviewPanel = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ShowPreviewPanelDateTimeChoices)); + OnPropertyChanged(nameof(PreviewPanelDateTimeChoicesVisibility)); + } + } + + public bool ShowModifiedDateInPreviewPanel + { + get => Settings.ShowModifiedDateInPreviewPanel; + set + { + Settings.ShowModifiedDateInPreviewPanel = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ShowPreviewPanelDateTimeChoices)); + OnPropertyChanged(nameof(PreviewPanelDateTimeChoicesVisibility)); + } + } + + public string PreviewPanelDateFormat + { + get => Settings.PreviewPanelDateFormat; + set + { + Settings.PreviewPanelDateFormat = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(PreviewPanelDateFormatDemo)); + } + } + + public string PreviewPanelTimeFormat + { + get => Settings.PreviewPanelTimeFormat; + set + { + Settings.PreviewPanelTimeFormat = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(PreviewPanelTimeFormatDemo)); + } + } + + public string PreviewPanelDateFormatDemo => DateTime.Now.ToString(PreviewPanelDateFormat, CultureInfo.CurrentCulture); + public string PreviewPanelTimeFormatDemo => DateTime.Now.ToString(PreviewPanelTimeFormat, CultureInfo.CurrentCulture); + + public bool ShowPreviewPanelDateTimeChoices => ShowCreatedDateInPreviewPanel || ShowModifiedDateInPreviewPanel; + + public Visibility PreviewPanelDateTimeChoicesVisibility => ShowCreatedDateInPreviewPanel || ShowModifiedDateInPreviewPanel ? Visibility.Visible : Visibility.Collapsed; + + + public List TimeFormatList { get; } = new() + { + "h:mm", + "hh:mm", + "H:mm", + "HH:mm", + "tt h:mm", + "tt hh:mm", + "h:mm tt", + "hh:mm tt", + "hh:mm:ss tt", + "HH:mm:ss" + }; + + + public List DateFormatList { get; } = new() + { + "dd/MM/yyyy", + "dd/MM/yyyy ddd", + "dd/MM/yyyy, dddd", + "dd-MM-yyyy", + "dd-MM-yyyy ddd", + "dd-MM-yyyy, dddd", + "dd.MM.yyyy", + "dd.MM.yyyy ddd", + "dd.MM.yyyy, dddd", + "MM/dd/yyyy", + "MM/dd/yyyy ddd", + "MM/dd/yyyy, dddd", + "yyyy-MM-dd", + "yyyy-MM-dd ddd", + "yyyy-MM-dd, dddd", + }; + + #endregion #region ActionKeyword diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml index b0708b793..5c92fc271 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/ExplorerSettings.xaml @@ -348,6 +348,57 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml new file mode 100644 index 000000000..a2910f230 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs new file mode 100644 index 000000000..878832e4f --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Explorer/Views/PreviewPanel.xaml.cs @@ -0,0 +1,101 @@ +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using Flow.Launcher.Infrastructure.Image; +using Flow.Launcher.Plugin.Explorer.Search; + +namespace Flow.Launcher.Plugin.Explorer.Views; + +#nullable enable + +public partial class PreviewPanel : UserControl, INotifyPropertyChanged +{ + private string FilePath { get; } + public string FileSize { get; } = ""; + public string CreatedAt { get; } = ""; + public string LastModifiedAt { get; } = ""; + private ImageSource _previewImage = new BitmapImage(); + private Settings Settings { get; } + + public ImageSource PreviewImage + { + get => _previewImage; + private set + { + _previewImage = value; + OnPropertyChanged(); + } + } + + 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 Visibility FileInfoVisibility => + Settings.ShowFileSizeInPreviewPanel || + Settings.ShowCreatedDateInPreviewPanel || + Settings.ShowModifiedDateInPreviewPanel + ? Visibility.Visible + : Visibility.Collapsed; + + public PreviewPanel(Settings settings, string filePath) + { + InitializeComponent(); + + Settings = settings; + + FilePath = filePath; + + if (Settings.ShowFileSizeInPreviewPanel) + { + var fileSize = new FileInfo(filePath).Length; + FileSize = ResultManager.ToReadableSize(fileSize, 2); + } + + if (Settings.ShowCreatedDateInPreviewPanel) + { + CreatedAt = File + .GetCreationTime(filePath) + .ToString( + $"{Settings.PreviewPanelDateFormat} {Settings.PreviewPanelTimeFormat}", + CultureInfo.CurrentCulture + ); + } + + if (Settings.ShowModifiedDateInPreviewPanel) + { + LastModifiedAt = File + .GetLastWriteTime(filePath) + .ToString( + $"{Settings.PreviewPanelDateFormat} {Settings.PreviewPanelTimeFormat}", + CultureInfo.CurrentCulture + ); + } + + _ = LoadImageAsync(); + } + + private async Task LoadImageAsync() + { + PreviewImage = await ImageLoader.LoadAsync(FilePath, true).ConfigureAwait(false); + } + + 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/plugin.json b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json index fac4621b0..60c1b11b5 100644 --- a/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Explorer/plugin.json @@ -10,7 +10,7 @@ "Name": "Explorer", "Description": "Find and manage files and folders via Windows Search or Everything", "Author": "Jeremy Wu", - "Version": "3.1.5", + "Version": "3.1.6", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Explorer.dll", diff --git a/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/vi.xaml new file mode 100644 index 000000000..58fcba3eb --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginIndicator/Languages/vi.xaml @@ -0,0 +1,9 @@ + + + + Activate {0} plugin action keyword + + Chỉ báo plugin + Cung cấp plugin gợi ý từ hành động + + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj index 92500ae6a..b438305d6 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Flow.Launcher.Plugin.PluginsManager.csproj @@ -6,6 +6,7 @@ true true false + en @@ -27,7 +28,7 @@ PreserveNewest - + PreserveNewest @@ -36,4 +37,4 @@ PreserveNewest - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png new file mode 100644 index 000000000..5d3ed0ace Binary files /dev/null and b/Plugins/Flow.Launcher.Plugin.PluginsManager/Images/zipfolder.png differ diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ar.xaml index ca5d964ab..e13a857a1 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ar.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ar.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/cs.xaml index 1351e69cb..3c377c27f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/cs.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/cs.xaml @@ -24,7 +24,6 @@ {0} z {1} {2}{3}Chcete tento zásuvný modul aktualizovat? Flow se po aktualizaci automaticky restartuje. {0} by {1} {2}{2}Would you like to update this plugin? Aktualizace Pluginu - Aktualizace tohoto pluginu je k dispozici, chcete ji zobrazit? Tento plugin je již nainstalován Stahování manifestu pluginu se nezdařilo Zkontrolujte, zda se můžete připojit k webu github.com. Tato chyba pravděpodobně znamená, že nemůžete instalovat nebo aktualizovat zásuvné moduly. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/da.xaml index f224af73f..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/da.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/da.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/de.xaml index 642c0d6a1..c69c3a2b5 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/de.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/de.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml index a89d9df21..de6a3a2fb 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/en.xaml @@ -26,7 +26,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es-419.xaml index f224af73f..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es-419.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es-419.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es.xaml index e3fa09615..75126dea6 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/es.xaml @@ -24,7 +24,6 @@ {0} por {1} {2}{3}¿Desea actualizar este complemento? Después de la actualización Flow se reiniciará automáticamente. {0} por {1} {2}{2}¿Desea actualizar este complemento? Actualizar complemento - Este complemento tiene una actualización, ¿desea verla? Este complemento ya está instalado La descarga del manifiesto del complemento ha fallado Por favor, compruebe que puede establecer conexión con github.com. Este error significa que es posible que no pueda instalar o actualizar complementos. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/fr.xaml index c36e4c39c..d40f2f9da 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/fr.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/fr.xaml @@ -24,7 +24,6 @@ {0} par {1} {2}{3}Voulez-vous mettre à jour ce plugin ? Après la mise à jour Flow redémarrera automatiquement. {0} par {1} {2}{2}Voulez-vous mettre à jour ce plugin ? Mise à jour du Plugin - Ce plugin a une mise à jour, voulez-vous le voir ? Ce plugin est déjà installé Échec du téléchargement du Plugin Manifest Veuillez vérifier si vous pouvez vous connecter à github.com. Cette erreur signifie que vous ne serez pas en mesure d'installer ou de mettre à jour des plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/it.xaml index e2ae667c1..9b6cf3068 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/it.xaml @@ -6,9 +6,9 @@ Download completato Errore: non è possibile scaricare il plugin {0} da {1} {2}{3}Vuoi disinstallare questo plugin? Dopo la disinstallazione, Flow si riavvierà automaticamente. - {0} by {1} {2}{2}Would you like to uninstall this plugin? + {0} di {1} {2}{2}Vuoi disinstallare questo plugin? {0} da {1} {2}{3}Vuoi installare questo plugin? Dopo l'installazione, Flow si riavvierà automaticamente. - {0} by {1} {2}{2}Would you like to install this plugin? + {0} di {1} {2}{2}Vuoi installare questo plugin? Installazione del plugin Installazione del Plugin Scarica e installa {0} @@ -18,30 +18,29 @@ Errore: esiste già un plugin che ha la stessa o maggiore versione con {0}. Errore durante l'installazione del plugin Errore durante il tentativo di installare {0} - Error uninstalling plugin + Errore durante la disinstallazione del plugin Nessun aggiornamento disponibile Tutti i plugin sono aggiornati {0} da {1} {2}{3}Vuoi aggiornare questo plugin? Dopo l'aggiornamento, Flow si riavvierà automaticamente. - {0} by {1} {2}{2}Would you like to update this plugin? + {0} di {1} {2}{2}Vuoi aggiornare questo plugin? Aggiornamento del plugin - Questo plugin ha un aggiornamento, vuoi vederlo? Questo plugin è già stato installato Download del manifesto del plugin fallito Controlla se puoi connetterti a github.com. Questo errore significa che potresti non essere in grado di installare o aggiornare i plugin. - Update all plugins - Would you like to update all plugins? - Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. - Would you like to update {0} plugins? - {0} plugins successfully updated. Restarting Flow, please wait... - Plugin {0} successfully updated. Restarting Flow, please wait... + Aggiorna tutti i plugin + Vuoi aggiornare tutti i plugin? + Vuoi aggiornare {0} plugin?{1}Flow Launcher verrà riavviato dopo aver aggiornato tutti i plugin. + Vuoi aggiornare {0} plugin? + {0} plugin aggiornati con successo. Riavviando Flow, attendere... + Il plugin {0} aggiornato con successo. Riavviando Flow, attendere... Installazione da una fonte sconosciuta Stai installando questo plugin da una fonte sconosciuta e potrebbe contenere potenziali rischi!{0}{0}Si prega di assicurarsi di capire la provenienza di questo plugin e se sia sicuro.{0}{0}Vuoi comunque continuare?{0}{0}(Puoi disattivare questo avviso dalle impostazioni) - Plugin {0} successfully installed. Please restart Flow. - Plugin {0} successfully uninstalled. Please restart Flow. - Plugin {0} successfully updated. Please restart Flow. - {0} plugins successfully updated. Please restart Flow. - Plugin {0} has already been modified. Please restart Flow before making any further changes. + Il plugin {0} installato con successo. Riavviare Flow. + Il plugin {0} disinstallato con successo. Riavviare Flow. + Il plugin {0} aggiornato con successo. Riavviare Flow. + {0} plugin aggiornato con successo. Riavviare Flow. + Il plugin {0} è già stato modificato. Riavviare Flow prima di fare altre modifiche. Gestore dei plugin @@ -60,5 +59,5 @@ Avviso di installazione da sorgenti sconosciute - Automatically restart Flow Launcher after installing/uninstalling/updating plugins + Riavvia automaticamente Flow Launcher dopo l'installazione/disinstallazione/aggiornamento dei plugin diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ja.xaml index f224af73f..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ja.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ja.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ko.xaml index d95a5d6a5..9bcf0a74b 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ko.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ko.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? 플러그인 업데이트 - This plugin has an update, would you like to see it? 이미 설치된 플러그인입니다 플러그인 목록 다운로드 실패 Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nb.xaml index f224af73f..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nb.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nb.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nl.xaml index f224af73f..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nl.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/nl.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pl.xaml index f224af73f..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pl.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pl.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-br.xaml index 949607f42..8640625c2 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-br.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? Este plugin já está instalado Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-pt.xaml index 7d94d6b33..c69317276 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-pt.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/pt-pt.xaml @@ -24,7 +24,6 @@ {0} de {1} {2}{3}Tem a certeza de que pretende atualizar este plugin? Após a atualização, Flow Launcher será reiniciado. {0} de {1} {2}{2}Gostaria de atualizar este plugin? Atualização de plugin - Existe uma atualização para este plugin. Deseja ver? Este plugin já está instalado Erro ao descarregar o manifesto do plugin Verifique se consegue estabelecer ligação a github.com. Este erro significa que pode não ser possível instalar ou atualizar os plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ru.xaml index f224af73f..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/ru.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml index 016842da9..1982dd5cf 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sk.xaml @@ -24,7 +24,6 @@ {0} od {1} {2}{3}Chcete aktualizovať tento plugin? Po aktualizácii sa Flow automaticky reštartuje. {0} od {1} {2}{2}Chcete aktualizovať tento plugin? Aktualizácia pluginu - Aktualizácia pre tento plugin je k dispozícii, chcete ju zobraziť? Tento plugin je už nainštalovaný Stiahnutie manifestu pluginu zlyhalo Skontrolujte, či sa môžete pripojiť ku github.com. Táto chyba znamená, že pravdepodobne nemôžete pluginy inštalovať alebo aktualizovať. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sr.xaml index f224af73f..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sr.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/sr.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/tr.xaml index f224af73f..723af440d 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/tr.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/tr.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? Plugin Update - This plugin has an update, would you like to see it? This plugin is already installed Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/uk-UA.xaml index b4d60f5b6..db863b70f 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/uk-UA.xaml @@ -24,7 +24,6 @@ {0} від {1} {2}{3}Бажаєте оновити цей плагін? Після оновлення Flow буде автоматично перезапущено. {0} від {1} {2}{2}Бажаєте оновити цей плагін? Оновлення плагіна - Цей плагін має оновлення, бажаєте його переглянути? Цей плагін вже встановлено Не вдалося завантажити маніфест плагіна Будь ласка, перевірте, чи можете ви підключитися до github.com. Ця помилка означає, що ви не можете встановлювати або оновлювати плагіни. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/vi.xaml new file mode 100644 index 000000000..b41ad44e9 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/vi.xaml @@ -0,0 +1,63 @@ + + + + + Plugin đang được tải + Đã tải xuống thành công! + Lỗi: Không thể tải plugin xuống + {0} bởi {1} {2}{3}Bạn có muốn gỡ cài đặt plugin này không? Sau khi gỡ cài đặt Flow sẽ tự động khởi động lại. + {0} by {1} {2}{2}Would you like to uninstall this plugin? + {0} bởi {1} {2}{3}Bạn có muốn gỡ cài đặt plugin này không? Sau khi gỡ cài đặt Flow sẽ tự động khởi động lại. + {0} by {1} {2}{2}Would you like to install this plugin? + Đã cài đặt plugin + Cài đặt Plugin + Tải về và cài đặt + Đã gỡ cài đặt plugin + Đã cài đặt thành công plugin. Đang khởi động lại Flow, vui lòng đợi... + Không thể tìm thấy tệp siêu dữ liệu plugin.json từ tệp zip được giải nén. + Lỗi: Đã tồn tại một plugin có phiên bản tương tự hoặc cao hơn với {0}. + Lỗi cài đặt plugin + Đã xảy ra lỗi khi cố gắng cài đặt {0} + Lỗi cài đặt plugin + Không có cập nhật + Tất cả các plugin đều được cập nhật + {0} bởi {1} {2}{3}Bạn có muốn gỡ cài đặt plugin này không? Sau khi gỡ cài đặt Flow sẽ tự động khởi động lại. + {0} by {1} {2}{2}Would you like to update this plugin? + Cập nhật plugin + Đã cài đặt plugin + Tải xuống bản kê khai plugin không thành công + Vui lòng kiểm tra xem bạn có thể kết nối với github.com không. Lỗi này có nghĩa là bạn không thể cài đặt hoặc cập nhật plugin. + Update all plugins + Would you like to update all plugins? + Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. + Would you like to update {0} plugins? + {0} plugins successfully updated. Restarting Flow, please wait... + Plugin {0} successfully updated. Restarting Flow, please wait... + Cài đặt từ một nguồn không xác định + Bạn đang cài đặt plugin này từ một nguồn không xác định và nó có thể chứa những rủi ro tiềm ẩn!{0}{0}Hãy đảm bảo rằng bạn hiểu plugin này đến từ đâu và nó an toàn.{0}{0}Bạn vẫn muốn tiếp tục chứ? {0}{0}(Bạn có thể tắt cảnh báo này thông qua cài đặt) + + Plugin {0} successfully installed. Please restart Flow. + Plugin {0} successfully uninstalled. Please restart Flow. + Plugin {0} successfully updated. Please restart Flow. + {0} plugins successfully updated. Please restart Flow. + Plugin {0} has already been modified. Please restart Flow before making any further changes. + + + Trình quản lý plugin + Quản lý cài đặt, gỡ cài đặt hoặc cập nhật plugin Flow Launcher + Không rõ tác giả + + + Mở website + Truy cập trang web của plugin + Xem mã nguồn + Xem mã nguồn của plugin + Đề xuất cải tiến hoặc gửi vấn đề + Đề xuất cải tiến hoặc gửi vấn đề cho nhà phát triển plugin + Đi tới kho plugin của Flow + Truy cập kho lưu trữ PluginsManifest để xem các bản gửi plugin do cộng đồng gửi + + + Cảnh báo cài đặt từ nguồn không xác định + Automatically restart Flow Launcher after installing/uninstalling/updating plugins + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml index fd6b09159..676c20a73 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-cn.xaml @@ -24,15 +24,14 @@ {0} by {1} {2}{3}您要更新此插件吗? 更新后,Flow Launcher 将自动重启。 {0} 作者: {1} {2}{2}您想要更新这个插件吗? 插件更新 - 该插件有更新,您想看看吗? 此插件已安装 插件列表下载失败 请检查您是否可以连接到 github.com。这个错误意味着您可能无法安装或更新插件。 更新所有插件 您想要更新所有插件吗? - Would you like to update {0} plugins?{1}Flow Launcher will restart after updating all plugins. - Would you like to update {0} plugins? - {0} plugins successfully updated. Restarting Flow, please wait... + 您想要更新 {0} 插件吗?{1}更新所有插件后 Flow Launcher 将重启。 + 您想要更新 {0} 插件吗? + 插件 {0} 更新成功。正在重新启动 Flow Launcher,请稍候..…… 插件{0}更新成功。正在重新启动 Flow Launcher,请稍候... 从未知源安装 您正在从未知源安装此插件,它可能包含潜在风险!{0}{0}请确保您了解来源以及安全性。{0}{0}您想要继续吗?{0}{0}(您可以通过设置关闭此警告) @@ -40,7 +39,7 @@ 成功安装插件{0}。请重新启动 Flow Launcher。 成功卸载插件{0}。请重新启动 Flow Launcher。 成功更新插件{0}。请重新启动 Flow Launcher。 - {0} plugins successfully updated. Please restart Flow. + 插件 {0} 更新成功。请重新启动 Flow Launcher。 插件 {0} 已被修改。请在进行任何进一步更改之前重新启动Flow。 diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-tw.xaml index 3505c4a7d..ae37579dc 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-tw.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Languages/zh-tw.xaml @@ -24,7 +24,6 @@ {0} by {1} {2}{3}Would you like to update this plugin? After the update Flow will automatically restart. {0} by {1} {2}{2}Would you like to update this plugin? 擴充功能更新 - This plugin has an update, would you like to see it? 已安裝此擴充功能 Plugin Manifest Download Failed Please check if you can connect to github.com. This error means you may not be able to install or update plugins. diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs index 8cd58ac52..15cbda7f2 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/PluginsManager.cs @@ -3,11 +3,9 @@ using Flow.Launcher.Core.Plugin; using Flow.Launcher.Infrastructure; using Flow.Launcher.Infrastructure.Http; using Flow.Launcher.Infrastructure.Logger; -using Flow.Launcher.Infrastructure.UserSettings; using Flow.Launcher.Plugin.SharedCommands; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; @@ -99,13 +97,12 @@ namespace Flow.Launcher.Plugin.PluginsManager if (Context.API.GetAllPlugins() .Any(x => x.Metadata.ID == plugin.ID && x.Metadata.Version.CompareTo(plugin.Version) < 0)) { - if (MessageBox.Show(Context.API.GetTranslation("plugin_pluginsmanager_update_exists"), - Context.API.GetTranslation("plugin_pluginsmanager_update_title"), - MessageBoxButton.YesNo) == MessageBoxResult.Yes) - Context - .API - .ChangeQuery( - $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {plugin.Name}"); + var updateDetail = !plugin.IsFromLocalInstallPath ? plugin.Name : plugin.LocalInstallPath; + + Context + .API + .ChangeQuery( + $"{Context.CurrentPluginMetadata.ActionKeywords.FirstOrDefault()} {Settings.UpdateCommand} {updateDetail}"); var mainWindow = Application.Current.MainWindow; mainWindow.Show(); @@ -147,12 +144,17 @@ namespace Flow.Launcher.Plugin.PluginsManager try { - if (File.Exists(filePath)) + if (!plugin.IsFromLocalInstallPath) { - File.Delete(filePath); - } + if (File.Exists(filePath)) + File.Delete(filePath); - await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); + await Http.DownloadAsync(plugin.UrlDownload, filePath).ConfigureAwait(false); + } + else + { + filePath = plugin.LocalInstallPath; + } Install(plugin, filePath); } @@ -193,24 +195,38 @@ namespace Flow.Launcher.Plugin.PluginsManager { await PluginsManifest.UpdateManifestAsync(token, usePrimaryUrlOnly); + var pluginFromLocalPath = null as UserPlugin; + var updateFromLocalPath = false; + + if (FilesFolders.IsZipFilePath(search, checkFileExists: true)) + { + pluginFromLocalPath = Utilities.GetPluginInfoFromZip(search); + pluginFromLocalPath.LocalInstallPath = search; + updateFromLocalPath = true; + } + + var updateSource = !updateFromLocalPath + ? PluginsManifest.UserPlugins + : new List { pluginFromLocalPath }; + var resultsForUpdate = ( from existingPlugin in Context.API.GetAllPlugins() - join pluginFromManifest in PluginsManifest.UserPlugins - on existingPlugin.Metadata.ID equals pluginFromManifest.ID - where String.Compare(existingPlugin.Metadata.Version, pluginFromManifest.Version, + join pluginUpdateSource in updateSource + on existingPlugin.Metadata.ID equals pluginUpdateSource.ID + where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version, StringComparison.InvariantCulture) < - 0 // if current version precedes manifest version + 0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest) && !PluginManager.PluginModified(existingPlugin.Metadata.ID) select new { - pluginFromManifest.Name, - pluginFromManifest.Author, + pluginUpdateSource.Name, + pluginUpdateSource.Author, CurrentVersion = existingPlugin.Metadata.Version, - NewVersion = pluginFromManifest.Version, + NewVersion = pluginUpdateSource.Version, existingPlugin.Metadata.IcoPath, PluginExistingMetadata = existingPlugin.Metadata, - PluginNewUserPlugin = pluginFromManifest + PluginNewUserPlugin = pluginUpdateSource }).ToList(); if (!resultsForUpdate.Any()) @@ -261,13 +277,21 @@ namespace Flow.Launcher.Plugin.PluginsManager _ = Task.Run(async delegate { - if (File.Exists(downloadToFilePath)) + if (!x.PluginNewUserPlugin.IsFromLocalInstallPath) { - File.Delete(downloadToFilePath); - } + if (File.Exists(downloadToFilePath)) + { + File.Delete(downloadToFilePath); + } - await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath) - .ConfigureAwait(false); + await Http.DownloadAsync(x.PluginNewUserPlugin.UrlDownload, downloadToFilePath) + .ConfigureAwait(false); + } + else + { + downloadToFilePath = x.PluginNewUserPlugin.LocalInstallPath; + } + PluginManager.UpdatePlugin(x.PluginExistingMetadata, x.PluginNewUserPlugin, downloadToFilePath); @@ -396,7 +420,7 @@ namespace Flow.Launcher.Plugin.PluginsManager results = results.Prepend(updateAllResult); } - return Search(results, search); + return !updateFromLocalPath ? Search(results, search) : results.ToList(); } internal bool PluginExists(string id) @@ -470,6 +494,42 @@ namespace Flow.Launcher.Plugin.PluginsManager return new List { result }; } + internal List InstallFromLocalPath(string localPath) + { + var plugin = Utilities.GetPluginInfoFromZip(localPath); + + plugin.LocalInstallPath = localPath; + + return new List + { + new Result + { + Title = $"{plugin.Name} by {plugin.Author}", + SubTitle = plugin.Description, + IcoPath = plugin.IcoPath, + Action = e => + { + if (Settings.WarnFromUnknownSource) + { + if (!InstallSourceKnown(plugin.Website) + && MessageBox.Show(string.Format( + Context.API.GetTranslation("plugin_pluginsmanager_install_unknown_source_warning"), + Environment.NewLine), + Context.API.GetTranslation( + "plugin_pluginsmanager_install_unknown_source_warning_title"), + MessageBoxButton.YesNo) == MessageBoxResult.No) + return false; + } + + Application.Current.MainWindow.Hide(); + _ = InstallOrUpdateAsync(plugin); + + return ShouldHideWindow; + } + } + }; + } + private bool InstallSourceKnown(string url) { var author = url.Split('/')[3]; @@ -489,6 +549,9 @@ namespace Flow.Launcher.Plugin.PluginsManager && search.Split('.').Last() == zip) return InstallFromWeb(search); + if (FilesFolders.IsZipFilePath(search, checkFileExists: true)) + return InstallFromLocalPath(search); + var results = PluginsManifest .UserPlugins @@ -522,10 +585,13 @@ namespace Flow.Launcher.Plugin.PluginsManager if (!File.Exists(downloadedFilePath)) throw new FileNotFoundException($"Plugin {plugin.ID} zip file not found at {downloadedFilePath}", downloadedFilePath); + try { PluginManager.InstallPlugin(plugin, downloadedFilePath); - File.Delete(downloadedFilePath); + + if (!plugin.IsFromLocalInstallPath) + File.Delete(downloadedFilePath); } catch (FileNotFoundException e) { diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs index 792891ad1..9800fa020 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Utilities.cs @@ -1,5 +1,10 @@ -using ICSharpCode.SharpZipLib.Zip; +using Flow.Launcher.Core.ExternalPlugins; +using Flow.Launcher.Infrastructure.UserSettings; +using ICSharpCode.SharpZipLib.Zip; +using Newtonsoft.Json; using System.IO; +using System.IO.Compression; +using System.Linq; namespace Flow.Launcher.Plugin.PluginsManager { @@ -55,5 +60,28 @@ namespace Flow.Launcher.Plugin.PluginsManager return string.Empty; } + + internal static UserPlugin GetPluginInfoFromZip(string filePath) + { + var plugin = null as UserPlugin; + + using (ZipArchive archive = System.IO.Compression.ZipFile.OpenRead(filePath)) + { + var pluginJsonPath = archive.Entries.FirstOrDefault(x => x.Name == "plugin.json").ToString(); + ZipArchiveEntry pluginJsonEntry = archive.GetEntry(pluginJsonPath); + + if (pluginJsonEntry != null) + { + using (StreamReader reader = new StreamReader(pluginJsonEntry.Open())) + { + string pluginJsonContent = reader.ReadToEnd(); + plugin = JsonConvert.DeserializeObject(pluginJsonContent); + plugin.IcoPath = "Images\\zipfolder.png"; + } + } + } + + return plugin; + } } } diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml index 18be0d2ca..f93f7799a 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/Views/PluginsManagerSettings.xaml @@ -7,26 +7,21 @@ d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"> - - - - - + + diff --git a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json index 4e475f171..438a407e2 100644 --- a/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.PluginsManager/plugin.json @@ -6,7 +6,7 @@ "Name": "Plugins Manager", "Description": "Management of installing, uninstalling or updating Flow Launcher plugins", "Author": "Jeremy Wu", - "Version": "3.1.0", + "Version": "3.1.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.PluginsManager.dll", diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj index 861fc3197..876bac1e7 100644 --- a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Flow.Launcher.Plugin.ProcessKiller.csproj @@ -12,6 +12,7 @@ true false false + en @@ -54,4 +55,4 @@ - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/vi.xaml new file mode 100644 index 000000000..0bf065ee1 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.ProcessKiller/Languages/vi.xaml @@ -0,0 +1,11 @@ + + + + Buộc Tắt tiến trình + Tắt các tiến trình đang chạy từ Flow Launcher + + tiêu diệt tất cả các phiên bản của "{0}" + Buộc tắt các tiến trình {0} + Tắt tất cả phên bản + + diff --git a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj index e9c680824..132c0c705 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj +++ b/Plugins/Flow.Launcher.Plugin.Program/Flow.Launcher.Plugin.Program.csproj @@ -12,6 +12,7 @@ true false false + en @@ -59,6 +60,7 @@ + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml index e62854305..dda427996 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ar.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml index 7be93ad14..9a703d3de 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/cs.xaml @@ -71,6 +71,7 @@ Spustit jako správce Otevřít umístění složky Zakázat zobrazování tohoto programu + Open target folder Program  Vyhledávání programů ve Flow Launcheru diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml index 905f7e787..676eb76cb 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/da.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml index 5ab3eb9ec..293dbc9c8 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/de.xaml @@ -71,6 +71,7 @@ Als Administrator ausführen Enthaltenden Ordner öffnen Disable this program from displaying + Open target folder Programm Suche Programme mit Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml index d7e87b50b..25ceac3bb 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/en.xaml @@ -32,6 +32,8 @@ When enabled, Flow will load programs from the PATH environment variable Hide app path For executable files such as UWP or lnk, hide the file path from being visible + Hide uninstallers + Hides programs with common uninstaller names, such as unins000.exe Search in Program Description Flow will search program's description Suffixes @@ -92,4 +94,4 @@ This app is not intended to be run as administrator Unable to run {0} - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml index 4a18143bf..375dcd041 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/es-419.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml index 4955c4ea2..d4eccc8ac 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/es.xaml @@ -71,6 +71,7 @@ Ejecutar como administrador Abrir carpeta contenedora Desactivar la visualización de este programa + Abrir carpeta de destino Programa Busca programas en Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml index 91f5e3ee5..d8b3dc5dd 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/fr.xaml @@ -71,6 +71,7 @@ Exécuter en tant qu'administrateur Ouvrir le répertoire Désactiver l'affichage de ce programme + Ouvrir le répertoire cible Programme Rechercher des programmes dans Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml index d13f926d6..e73c8d24e 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/it.xaml @@ -6,42 +6,42 @@ Cancella Modifica Aggiungi - Name - Enable + Nome + Abilita Abilitato - Disable + Disabilita Status Abilitato Disabled - Location - All Programs + Posizione + Tutti i programmi File Type - Reindex - Indexing + Reindicizza + Indicizzando Index Sources Options UWP Apps When enabled, Flow will load UWP Applications Start Menu - When enabled, Flow will load programs from the start menu + Se abilitato, Flow caricherà i programmi dal Menu Start Registry - When enabled, Flow will load programs from the registry + Se abilitato, Flow caricherà i programmi dal Registro PATH When enabled, Flow will load programs from the PATH environment variable - Hide app path - For executable files such as UWP or lnk, hide the file path from being visible - Search in Program Description + Nascondi percorso app + Per i file eseguibili come UWP o lnk, nascondere il percorso del file + Cerca nella Descrizione del Programma Flow will search program's description - Suffixes - Max Depth + Suffissi + Profondità max - Directory - Browse - File Suffixes: - Maximum Search Depth (-1 is unlimited): + Cartella + Sfoglia + Suffissi dei file: + Profondità massima di ricerca (-1 è illimitata): - Please select a program source - Are you sure you want to delete the selected program sources? + Seleziona la sorgente del programma + Sei sicuro di voler cancellare le sorgenti dei programmi selezionate? Another program source with the same location already exists. Program Source @@ -49,11 +49,11 @@ Aggiorna Program Plugin will only index files with selected suffixes and .url files with selected protocols. - Successfully updated file suffixes - File suffixes can't be empty + Suffissi dei file aggiornati con successo + I suffissi del file non possono essere vuoti Protocols can't be empty - File Suffixes + Suffissi dei file URL Protocols Steam Games Epic Games @@ -67,17 +67,18 @@ Insert protocols of .url files you want to index. Protocols should be separated by ';', and should end with "://". (ex>ftp://;mailto://) - Run As Different User - Run As Administrator + Esegui Come Utente Differente + Esegui Come Amministratore Apri percorso file - Disable this program from displaying + Disabilita questo programma dalla visualizzazione + Open target folder - Program - Search programs in Flow Launcher + Programma + Cerca programmi in Flow Launcher - Invalid Path + Percorso non valido - Customized Explorer + Esplora Risorse personalizzato Parametri You can customized the explorer used for opening the container folder by inputing the Environmental Variable of the explorer you want to use. It will be useful to use CMD to test whether the Environmental Variable is available. Inserisci i parametri personalizzati che vuoi aggiungere per il tuo Esplora Risorse personalizzato. %s per la cartella superiore, %f per il percorso completo (che funziona solo per win32). Controlla il sito dell'Esplora Risorse per i dettagli. diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml index 863735769..6c35d7df1 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ja.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml index 9741a7fe2..2857194c7 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ko.xaml @@ -71,6 +71,7 @@ 관리자 권한으로 실행 포함된 폴더 열기 이 프로그램 표시 비활성화 + Open target folder 프로그램 Flow Launcher에서 프로그램을 검색합니다 diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml index e62854305..dda427996 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/nb.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml index 3c3b20afd..972cbe8d6 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/nl.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml index c46a92006..d651fb0b9 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/pl.xaml @@ -71,6 +71,7 @@ Uruchom jako administrator Open containing folder Disable this program from displaying + Open target folder Programy Szukaj i uruchamiaj programy z poziomu Flow Launchera diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml index b943ffe97..f1cb7d7a2 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-br.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml index 669fe3182..0832c2150 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/pt-pt.xaml @@ -71,6 +71,7 @@ Executar como administrador Abrir pasta de destino Desativar exibição deste programa + Abrir pasta de destino Programas Pesquisa de programas com o Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml index 9fdb70d4f..ebc30e161 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/ru.xaml @@ -71,6 +71,7 @@ Запустить от имени администратора Открыть содержащую папку Отключить отображение этой программы + Open target folder Программа Поиск программ в Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml index ee62a823e..a669d256b 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/sk.xaml @@ -71,6 +71,7 @@ Spustiť ako správca Otvoriť umiestnenie priečinka Zakázať zobrazovanie tohto programu + Otvoriť cieľový pričinok Program Vyhľadávanie programov vo Flow Launcheri diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml index f77bce039..6d995e1ab 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/sr.xaml @@ -71,6 +71,7 @@ Run As Administrator Open containing folder Disable this program from displaying + Open target folder Program Search programs in Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml index 3d1f087f2..675c9f132 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/tr.xaml @@ -71,6 +71,7 @@ Yönetici Olarak Çalıştır Open containing folder Disable this program from displaying + Open target folder Program Programları Flow Launcher'tan arayın diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml index f4a9926e8..344695d1e 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/uk-UA.xaml @@ -71,6 +71,7 @@ Запустити від імені адміністратора Відкрити папку Вимкнути відображення цієї програми + Відкрити цільову папку Програма Пошук програм у Flow Launcher diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/vi.xaml new file mode 100644 index 000000000..7ec467566 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/vi.xaml @@ -0,0 +1,93 @@ + + + + + Khôi phục về mặc định + Xóa + Sửa + Thêm + Tên + Kích hoạt + Đã bật + Vô hiệu + Trạng thái + Đã bật + Vô hiệu hóa + Vị trí + Tất cả ứng dụng + Loại tệp + Chỉ số hoá + Nhập dữ liệu + Nguồn chỉ mục + Tùy chỉnh + UWP Apps + When enabled, Flow will load UWP Applications + Menu Khởi động + Khi được bật, Flow sẽ tải các chương trình từ menu bắt đầu + Đăng ký + Khi được bật, Flow sẽ tải các chương trình từ menu bắt đầu + ĐƯỜNG DẪN + Khi được bật, Flow sẽ tải các chương trình từ menu bắt đầu + Ẩn đường dẫn ứng dụng + Đối với các tệp thực thi như UWP hoặc lnk, hãy ẩn đường dẫn tệp để không hiển thị + Tìm kiếm trong Mô tả chương trình + Flow will search program's description + Hậu tố + Độ sâu tối đa: + + Danh bạ + Duyệt + Hậu tố tệp: + Độ sâu tìm kiếm tối đa (-1 là không giới hạn): + + Hãy chọn một nguồn dữ liệu + Bạn có chắc chắn là muốn xóa các đặt hàng đã chọn? + Another program source with the same location already exists. + + Program Source + Edit directory and status of this program source. + + Cập nhật + Program Plugin will only index files with selected suffixes and .url files with selected protocols. + Đã cập nhật thành công hậu tố tệp + Hậu tố tệp không được để trống + Trường cổng không được trống + + Hậu tố tệp + URL Protocols + Steam Games + Epic Games + Http/Https + Custom URL Protocols + Custom File Suffixes + + Insert file suffixes you want to index. Suffixes should be separated by ';'. (ex>bat;py) + + + Insert protocols of .url files you want to index. Protocols should be separated by ';', and should end with "://". (ex>ftp://;mailto://) + + + Xóa lựa chọn đã chọn + Chạy với quyền quản trị + Mở thư mục chứa + Vô hiệu hóa chương trình này hiển thị + Open target folder + + Chương trình + Tìm kiếm chương trình trong Flow Launcher + + Đường dẫn không hợp lệ + + Trình khám phá tùy chỉnh + Đối số + Bạn có thể tùy chỉnh trình thám hiểm được sử dụng để mở thư mục vùng chứa bằng cách nhập Biến môi trường của trình khám phá mà bạn muốn sử dụng. Sẽ rất hữu ích khi sử dụng CMD để kiểm tra xem Biến môi trường có sẵn hay không. + Nhập các đối số tùy chỉnh mà bạn muốn thêm cho trình khám phá tùy chỉnh của mình. %s cho thư mục mẹ, %f cho đường dẫn đầy đủ (chỉ hoạt động với win32). Kiểm tra trang web của nhà thám hiểm để biết chi tiết. + + + Thành công + Lỗi + Đã vô hiệu hóa thành công chương trình này khỏi hiển thị trong truy vấn của bạn + Ứng dụng này không nhằm mục đích chạy với tư cách quản trị viên + Không thể đọc {0} + + diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml index 3a6fbda5d..324bd3057 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-cn.xaml @@ -71,6 +71,7 @@ 以管理员身份运行 打开文件所在文件夹 禁止显示该程序 + Open target folder 程序 在 Flow Launcher 中搜索程序 diff --git a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml index d27b5f78f..7c72b1aa3 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Languages/zh-tw.xaml @@ -71,6 +71,7 @@ 以系統管理員身分執行 開啟檔案位置 Disable this program from displaying + Open target folder 程式 在 Flow Launcher 中搜尋程式 diff --git a/Plugins/Flow.Launcher.Plugin.Program/Main.cs b/Plugins/Flow.Launcher.Plugin.Program/Main.cs index e0a7f23de..8bf1830e3 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Main.cs @@ -11,6 +11,7 @@ using Flow.Launcher.Plugin.Program.Programs; using Flow.Launcher.Plugin.Program.Views; using Flow.Launcher.Plugin.Program.Views.Models; using Microsoft.Extensions.Caching.Memory; +using Path = System.IO.Path; using Stopwatch = Flow.Launcher.Infrastructure.Stopwatch; namespace Flow.Launcher.Plugin.Program @@ -33,6 +34,17 @@ namespace Flow.Launcher.Plugin.Program private static readonly MemoryCacheOptions cacheOptions = new() { SizeLimit = 1560 }; private static MemoryCache cache = new(cacheOptions); + private static readonly string[] commonUninstallerNames = + { + "uninst.exe", + "unins000.exe", + "uninst000.exe", + "uninstall.exe" + }; + // For cases when the uninstaller is named like "Uninstall Program Name.exe" + private const string CommonUninstallerPrefix = "uninstall"; + private const string CommonUninstallerSuffix = ".exe"; + static Main() { } @@ -52,6 +64,7 @@ namespace Flow.Launcher.Plugin.Program .Concat(_uwps) .AsParallel() .WithCancellation(token) + .Where(HideUninstallersFilter) .Where(p => p.Enabled) .Select(p => p.Result(query.Search, Context.API)) .Where(r => r?.Score > 0) @@ -68,6 +81,16 @@ namespace Flow.Launcher.Plugin.Program return result; } + private bool HideUninstallersFilter(IProgram program) + { + if (!_settings.HideUninstallers) return true; + if (program is not Win32 win32) return true; + var fileName = Path.GetFileName(win32.ExecutablePath); + return !commonUninstallerNames.Contains(fileName, StringComparer.OrdinalIgnoreCase) && + !(fileName.StartsWith(CommonUninstallerPrefix, StringComparison.OrdinalIgnoreCase) && + fileName.EndsWith(CommonUninstallerSuffix, StringComparison.OrdinalIgnoreCase)); + } + public async Task InitAsync(PluginInitContext context) { Context = context; diff --git a/Plugins/Flow.Launcher.Plugin.Program/Settings.cs b/Plugins/Flow.Launcher.Plugin.Program/Settings.cs index ca203f803..fb24f64d7 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Settings.cs +++ b/Plugins/Flow.Launcher.Plugin.Program/Settings.cs @@ -102,7 +102,7 @@ namespace Flow.Launcher.Plugin.Program // CustomSuffixes no longer contains custom suffixes // users has tweaked the settings // or this function has been executed once - if (UseCustomSuffixes == true || ProgramSuffixes == null) + if (UseCustomSuffixes == true || ProgramSuffixes == null) return; var suffixes = ProgramSuffixes.ToList(); foreach(var item in BuiltinSuffixesStatus) @@ -117,6 +117,7 @@ namespace Flow.Launcher.Plugin.Program public bool EnableStartMenuSource { get; set; } = true; public bool EnableDescription { get; set; } = false; public bool HideAppsPath { get; set; } = true; + public bool HideUninstallers { get; set; } = false; public bool EnableRegistrySource { get; set; } = true; public bool EnablePathSource { get; set; } = false; public bool EnableUWP { get; set; } = true; diff --git a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml index fa97de4f2..e5ca6967e 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml +++ b/Plugins/Flow.Launcher.Plugin.Program/Views/ProgramSetting.xaml @@ -85,6 +85,11 @@ Content="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath}" IsChecked="{Binding HideAppsPath}" ToolTip="{DynamicResource flowlauncher_plugin_program_enable_hidelnkpath_tooltip}" /> + ProgramSettingDisplayList { get; set; } public bool EnableDescription @@ -47,6 +47,16 @@ namespace Flow.Launcher.Plugin.Program.Views } } + public bool HideUninstallers + { + get => _settings.HideUninstallers; + set + { + Main.ResetCache(); + _settings.HideUninstallers = value; + } + } + public bool EnableRegistrySource { get => _settings.EnableRegistrySource; diff --git a/Plugins/Flow.Launcher.Plugin.Program/plugin.json b/Plugins/Flow.Launcher.Plugin.Program/plugin.json index 7f6b5f938..9ed7edbd9 100644 --- a/Plugins/Flow.Launcher.Plugin.Program/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Program/plugin.json @@ -4,7 +4,7 @@ "Name": "Program", "Description": "Search programs in Flow.Launcher", "Author": "qianlifeng", - "Version": "3.2.0", + "Version": "3.2.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Program.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj index dfbf54c3a..8f443214b 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj +++ b/Plugins/Flow.Launcher.Plugin.Shell/Flow.Launcher.Plugin.Shell.csproj @@ -12,8 +12,9 @@ true false false + en - + true portable @@ -24,7 +25,7 @@ 4 false - + pdbonly true @@ -39,13 +40,13 @@ - + PreserveNewest - + MSBuild:Compile @@ -56,9 +57,9 @@ PreserveNewest - + - - \ No newline at end of file + + diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml index ee473d80e..4e61940d1 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/it.xaml @@ -2,8 +2,8 @@ Sostituisci Win+R - Close Command Prompt after pressing any key - Press any key to close this window... + Chiudi il Prompt dei Comandi dopo aver premuto un tasto + Premi un tasto per chiudere questa finestra... Non chiudere il prompt dei comandi dopo l'esecuzione dei comandi Esegui sempre come amministratore Esegui come utente differente diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml index b7d02c558..bf3864c7b 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/ru.xaml @@ -1,17 +1,17 @@  - Replace Win+R + Заменить Win+R Close Command Prompt after pressing any key Press any key to close this window... - Do not close Command Prompt after command execution - Always run as administrator + Не закрывать командную строку после выполнения команды + Всегда запускать с правами администратора Run as different user - Shell + Оболочка Allows to execute system commands from Flow Launcher - this command has been executed {0} times + эта команда была выполнена {0} раз execute command through command shell Run As Administrator - Copy the command - Only show number of most used commands: + Скопировать команду + Показывать только самые используемые команды: diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/vi.xaml new file mode 100644 index 000000000..22d909686 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/vi.xaml @@ -0,0 +1,17 @@ + + + + Thay thế Win + R + Close Command Prompt after pressing any key + Press any key to close this window... + Không đóng dấu nhắc lệnh sau khi thực hiện lệnh + Luôn chạy với tư cách quản trị viên + Xóa lựa chọn đã chọn + Vỏ + Allows to execute system commands from Flow Launcher + lệnh này đã được thực thi {0} lần + thực thi lệnh thông qua lệnh shell + Chạy với quyền quản trị + Sao chép lệnh + Chỉ hiển thị số lượng lệnh được sử dụng nhiều nhất: + diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml index ace554327..2db287e39 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.Shell/Languages/zh-cn.xaml @@ -2,7 +2,7 @@ 替换 Win+R - Close Command Prompt after pressing any key + 按下任意键后关闭命令提示符 按下任意键以关闭此窗口... 执行后不关闭命令窗口 始终以管理员身份运行 diff --git a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs index f3c34d41d..921c6bc21 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/Main.cs +++ b/Plugins/Flow.Launcher.Plugin.Shell/Main.cs @@ -275,6 +275,8 @@ namespace Flow.Launcher.Plugin.Shell info.FileName = command; } + info.UseShellExecute = true; + break; } default: diff --git a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json index 6c2b2a184..e06c22c54 100644 --- a/Plugins/Flow.Launcher.Plugin.Shell/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Shell/plugin.json @@ -4,7 +4,7 @@ "Name": "Shell", "Description": "Provide executing commands from Flow Launcher", "Author": "qianlifeng", - "Version": "3.2.0", + "Version": "3.2.1", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Shell.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj index c7a722189..b797b3cf4 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj +++ b/Plugins/Flow.Launcher.Plugin.Sys/Flow.Launcher.Plugin.Sys.csproj @@ -12,8 +12,9 @@ true false false + en - + true portable @@ -24,7 +25,7 @@ 4 false - + pdbonly true @@ -39,7 +40,7 @@ - + MSBuild:Compile @@ -50,10 +51,10 @@ PreserveNewest - + PreserveNewest - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml index 41e005894..ddf8a4d71 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ar.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Exit Save Settings - Restart Flow Launcher" + Restart Flow Launcher Settings Reload Plugin Data Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml index 3e053b39f..b377ad3d2 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/cs.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Ukončit Save Settings - Restart Flow Launcher" + Restartovat Flow Launcher Nastavení Znovu načíst data pluginů Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml index 8c5e4d0ff..30beff7b5 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/da.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Afslut Save Settings - Restart Flow Launcher" + Restart Flow Launcher Indstillinger Reload Plugin Data Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml index 05a5458e1..1275f8b74 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es-419.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Salir Save Settings - Restart Flow Launcher" + Restart Flow Launcher Ajustes Reload Plugin Data Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml index f95e44dcf..c457b160c 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/es.xaml @@ -17,7 +17,7 @@ Abrir papelera de reciclaje Salir Guardar configuración - Reiniciar Flow Launcher" + Reinicia Flow Launcher Configuración Recargar datos del complemento Buscar actualizaciones diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml index a70994188..129c6a6fa 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/it.xaml @@ -5,25 +5,25 @@ Comando Descrizione - Shutdown - Restart - Restart With Advanced Boot Options - Log Off/Sign Out - Lock - Sleep - Hibernate - Index Option - Empty Recycle Bin - Open Recycle Bin + Spegni + Riavvia + Riavvia con Opzioni di Avvio Avanzate + Disconnetti + Blocca + Sospendi + Ibernazione + Opzioni di Indice + Svuota Cestino + Apri Cestino Esci - Save Settings - Restart Flow Launcher" + Salva Impostazioni + Riavvia Flow Launcher Impostazioni Ricarica i dati del plugin - Check For Update - Open Log Location - Flow Launcher Tips - Flow Launcher UserData Folder + Controlla Aggiornamenti + Apri Posizione dei Log + Consigli di Flow Launcher + Cartella UserData di Flow Launcher Attiva/Disattiva Modalità Di Gioco diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml index 9080cf314..bb00a0df6 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ja.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Save Settings - Restart Flow Launcher" + Flow Launcherを再起動する プラグインデータのリロード Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml index 5389cbb38..e2962ff34 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ko.xaml @@ -17,7 +17,7 @@ Open Recycle Bin 종료 Save Settings - Restart Flow Launcher" + Flow Launcher 재시작 설정 플러그인 데이터 새로고 Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml index 41e005894..ddf8a4d71 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nb.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Exit Save Settings - Restart Flow Launcher" + Restart Flow Launcher Settings Reload Plugin Data Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml index a71e28e98..00201fa0e 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/nl.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Afsluiten Save Settings - Restart Flow Launcher" + Flow Launcher herstarten Instellingen Reload Plugin Data Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml index 4549f193e..4c7592411 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pl.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Wyjdź Save Settings - Restart Flow Launcher" + Uruchom ponownie Flow Launchera Ustawienia Reload Plugin Data Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml index b80ddf621..a51c751df 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/pt-br.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Sair Save Settings - Restart Flow Launcher" + Reiniciar Flow Launcher Configurações Recarregar Dados de Plugin Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml index 927ccb0b6..3d3bcfc87 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/ru.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Выйти Save Settings - Restart Flow Launcher" + Restart Flow Launcher Настройки Перезагрузить данные плагинов Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml index 82d8c2fe4..c5cee316f 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sk.xaml @@ -17,7 +17,7 @@ Otvoriť kôš Ukončiť Uložiť nastavenia - Reštartovať Flow Launcher" + Reštartovať Flow Launcher Nastavenia Znova načítať údaje pluginov Skontrolovať aktualizácie diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml index fa6b54b81..a984954f9 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/sr.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Izlaz Save Settings - Restart Flow Launcher" + Restart Flow Launcher Podešavanja Reload Plugin Data Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml index 9670d8297..35850cb26 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/tr.xaml @@ -17,7 +17,7 @@ Open Recycle Bin Çıkı Save Settings - Restart Flow Launcher" + Flow Launcher'u Yeniden Başlat Ayarlar Reload Plugin Data Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml index 526144b43..8f2883bce 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/uk-UA.xaml @@ -17,7 +17,7 @@ Відкрити кошик Вийти Зберегти налаштування - Перезапустити Flow Launcher" + Перезапустити Flow Launcher Налаштування Перезавантажити дані плагінів Перевірити наявність оновлень diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/vi.xaml new file mode 100644 index 000000000..bb76debc3 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/vi.xaml @@ -0,0 +1,63 @@ + + + + + Lệnh + Mô Tả + + Shutdown + Restart + Restart With Advanced Boot Options + Log Off/Sign Out + Lock + Sleep + Hibernate + Index Option + Empty Recycle Bin + Open Recycle Bin + Thoát + Save Settings + Bắt đầu Flow-Launcher + Cài đặt + Dữ liệu plugin không tải + Check For Update + Open Log Location + Flow Launcher Tips + Flow Launcher UserData Folder + Toggle Game Mode + + + shutdown máy tính + Khởi động lại máy tính + Khởi động lại máy tính với Tùy chọn khởi động nâng cao cho chế độ An toàn và Gỡ lỗi, cũng như các tùy chọn khác + Đăng xuất + Khóa máy tính này + Chào mừng bạn đến với Trình khởi chạy luồng + Bắt đầu Flow-Launcher + Tweak Flow Launcher's settings + Đặt máy tính vào chế độ ngủ + Dọn sạch thùng rác + Open recycle bin + Tùy chọn lập chỉ mục + Máy tính ngủ đông + Lưu tất cả cài đặt Flow Launcher + Làm mới dữ liệu plugin với nội dung mới + Mở vị trí nhật ký của Flow Launcher + Kiểm tra bản cập nhật Flow Launcher mới + Truy cập tài liệu của Flow Launcher để được trợ giúp thêm và cách sử dụng các mẹo + Mở vị trí lưu trữ cài đặt của Flow Launcher + Toggle Game Mode + + + Thành công + Đã lưu tất cả cài đặt của Flow Launcher + Đã tải lại tất cả dữ liệu plugin hiện hành + Bạn có chắc chắn muốn tắt máy tính không? + Bạn có chắc muốn khởi động lại cấp này? + Bạn có chắc chắn muốn khởi động lại máy tính bằng Advanced Boot Options không? + Are you sure you want to log off? + + Lệnh hệ thống + Cung cấp các lệnh liên quan đến Hệ thống. ví dụ. tắt máy, khóa, cài đặt, v.v. + + diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml index be84d0e7f..cd0b6f166 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-cn.xaml @@ -11,20 +11,20 @@ Log Off/Sign Out Lock Sleep - Hibernate - Index Option - Empty Recycle Bin - Open Recycle Bin + 休眠 + 索引选项 + 清空回收站 + 打开回收站 退出 - Save Settings - Restart Flow Launcher" + 保存设置 + 重启 Flow Launcher 设置 重新加载插件数据 - Check For Update - Open Log Location - Flow Launcher Tips - Flow Launcher UserData Folder - Toggle Game Mode + 检查更新 + 打开日志文件夹 + Flow Launcher 提示 + Flow Launcher 用户数据文件夹 + 切换游戏模式 关闭电脑 @@ -46,7 +46,7 @@ 检查新的 Flow Launcher 更新 访问 Flow Launcher 的文档以获取更多帮助以及使用技巧 打开Flow Launcher 设置文件夹 - Toggle Game Mode + 切换游戏模式 成功 diff --git a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml index 62bf8f012..9934acc9f 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml +++ b/Plugins/Flow.Launcher.Plugin.Sys/Languages/zh-tw.xaml @@ -17,7 +17,7 @@ Open Recycle Bin 結束 Save Settings - Restart Flow Launcher" + 重新啟動 Flow Launcher 設定 重新載入插件資料 Check For Update diff --git a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json index 5276690e3..f7dea266a 100644 --- a/Plugins/Flow.Launcher.Plugin.Sys/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.Sys/plugin.json @@ -4,7 +4,7 @@ "Name": "System Commands", "Description": "Provide System related commands. e.g. shutdown,lock, setting etc.", "Author": "qianlifeng", - "Version": "3.1.1", + "Version": "3.1.2", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.Sys.dll", diff --git a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj index c03acefae..3db0cd0cb 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj +++ b/Plugins/Flow.Launcher.Plugin.Url/Flow.Launcher.Plugin.Url.csproj @@ -1,5 +1,5 @@  - + Library net7.0-windows @@ -11,8 +11,9 @@ true false false + en - + true portable @@ -23,7 +24,7 @@ 4 false - + pdbonly true @@ -33,7 +34,7 @@ 4 false - + PreserveNewest @@ -56,4 +57,4 @@ - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.Url/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.Url/Languages/ru.xaml index 418731021..5110f65ef 100644 --- a/Plugins/Flow.Launcher.Plugin.Url/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.Url/Languages/ru.xaml @@ -12,6 +12,6 @@ Open the typed URL from Flow Launcher Please set your browser path: - Choose + Выберите Application(*.exe)|*.exe|All files|*.* diff --git a/Plugins/Flow.Launcher.Plugin.Url/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.Url/Languages/vi.xaml new file mode 100644 index 000000000..41f87fe19 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.Url/Languages/vi.xaml @@ -0,0 +1,17 @@ + + + + Mở tìm kiếm + Cửa sổ mới + Tab mới + + Mở url:{0} + Không thể mở url:{0} + + Địa chỉ URL + Mở URL đã nhập từ Flow Launcher + + Vui lòng đặt đường dẫn trình duyệt của bạn: + Chọn + Ứng dụng(*.exe)|*.exe|Tất cả các tệp|*.* + diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/gist.png b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/gist.png index dd9fb6f00..a272f3fb5 100644 Binary files a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/gist.png and b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/gist.png differ diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/github.png b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/github.png index dd9fb6f00..a272f3fb5 100644 Binary files a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/github.png and b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/github.png differ diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/gmail.png b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/gmail.png index aaeff3020..e74f0d573 100644 Binary files a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/gmail.png and b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/gmail.png differ diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google.png b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google.png index 897151684..7c1df1f29 100644 Binary files a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google.png and b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google.png differ diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_drive.png b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_drive.png index ce2346a31..bff02c392 100644 Binary files a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_drive.png and b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_drive.png differ diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_maps.png b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_maps.png index 2413eec58..b086b7b74 100644 Binary files a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_maps.png and b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_maps.png differ diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_translate.png b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_translate.png index 63ff5c883..7ba66ece2 100644 Binary files a/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_translate.png and b/Plugins/Flow.Launcher.Plugin.WebSearch/Images/google_translate.png differ diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/it.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/it.xaml index 6be89607d..dc9bd7a2e 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/it.xaml +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/it.xaml @@ -1,7 +1,7 @@  - Search Source Setting + Impostazioni Sorgenti di Ricerca Apri ricerca in: Nuova Finestra Nuova Scheda @@ -12,40 +12,40 @@ Aggiungi Abilitato Abilitato - Disabled - Confirm - Action Keyword + Disabilitato + Conferma + Parola Chiave URL - Search - Use Search Query Autocomplete: - Autocomplete Data from: - Please select a web search + Cerca + Usa Autocompletamento Ricerca: + Autocompleta i dati da: + Seleziona una ricerca web Sei sicuro di voler eliminare {0}? - If you want to add a search for a particular website to Flow, first enter a dummy text string in the search bar of that website, and launch the search. Now copy the contents of the browser's address bar, and paste it in the URL field below. Replace your test string with {q}. For example, if you search for casino on Netflix, its address bar reads + Se vuoi aggiungere una ricerca di un particolare sito web a Flow, prima immetti una stringa di testo fittizia nella barra di ricerca di quel sito web, e avvia la ricerca. Ora copia il contenuto della barra degli indirizzi del browser e incollalo nel campo URL sotto. Sostituisci la stringa di prova con {q}. Per esempio, se cerchi 'casino' su Netflix, sulla barra degli indirizzi ci sarà https://www.netflix.com/search?q=Casino - Now copy this entire string and paste it in the URL field below. - Then replace casino with {q}. - Thus, the generic formula for a search on Netflix is https://www.netflix.com/search?q={q} + Ora copia l'intera stringa e incollala nel campo URL sotto. + Sostituisci 'casino' con {q}. + Così la formula generica per una ricerca su Netflix è https://www.netflix.com/search?q={q} - Title - Status - Select Icon - Icon + Titolo + Stato + Seleziona Icona + Icona Annulla - Invalid web search - Please enter a title - Please enter an action keyword - Please enter a URL - Action keyword already exists, please enter a different one + Ricerca web non valida + Inserisci un titolo + Inserisci una parola chiave + Inserisci un URL + La parola chiave esiste già, inserirne una diversa Successo - Hint: You do not need to place custom images in this directory, if Flow's version is updated they will be lost. Flow will automatically copy any images outside of this directory across to WebSearch's custom image location. + Suggerimento: Non è necessario inserire immagini personalizzate in questa cartella, se Flow viene aggiornato queste verranno perse. Flow copierà automaticamente tutte le immagini al di fuori di questa cartella nella posizione delle immagini personalizzate di WebSearch. - Web Searches - Allows to perform web searches + Ricerche Web + Consente di eseguire ricerche web diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ru.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ru.xaml index fab878992..a2ec9405a 100644 --- a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ru.xaml +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/ru.xaml @@ -5,8 +5,8 @@ Open search in: New Window New Tab - Set browser from path: - Choose + Установить браузер по пути: + Выберите Удалить Редактировать Добавить diff --git a/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/vi.xaml b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/vi.xaml new file mode 100644 index 000000000..a6de788b5 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.WebSearch/Languages/vi.xaml @@ -0,0 +1,51 @@ + + + + Cài đặt nguồn tìm kiếm + Mở tìm kiếm + Cửa sổ mới + Tab mới + Đặt trình duyệt từ đường dẫn: + Chọn + Xóa + Sửa + Thêm + Đã bật + Đã bật + Vô hiệu hóa + Xác nhận + Từ khóa hành động + Địa chỉ URL + Tìm kiếm + Sử dụng Tự động hoàn thành truy vấn tìm kiếm: + Tự động hoàn thành dữ liệu từ: + Vui lòng chọn tìm kiếm trên web + Bạn có chắc chắn muốn xóa {0} không? + If you want to add a search for a particular website to Flow, first enter a dummy text string in the search bar of that website, and launch the search. Now copy the contents of the browser's address bar, and paste it in the URL field below. Replace your test string with {q}. For example, if you search for casino on Netflix, its address bar reads + https://www.netflix.com/search?q=Casino + + Now copy this entire string and paste it in the URL field below. + Then replace casino with {q}. + Thus, the generic formula for a search on Netflix is https://www.netflix.com/search?q={q} + + + + + + Tiêu đề + Trạng thái + Chọn Biểu tượng + Biểu tượng + Hủy + Tìm kiếm trên web không hợp lệ + Vui lòng nhập tiêu đề + Vui lòng nhập từ khóa hành động + Hãy nhập URL + Từ khóa hành động đã tồn tại, vui lòng nhập một từ khóa khác + Thành công + Gợi ý: Bạn không cần đặt hình ảnh tùy chỉnh trong thư mục này, nếu phiên bản của Flow được cập nhật, chúng sẽ bị mất. Flow sẽ tự động sao chép mọi hình ảnh bên ngoài thư mục này sang vị trí hình ảnh tùy chỉnh của WebSearch. + + Tìm kiếm Web + Cho phép thực hiện tìm kiếm trên web + + diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj index 47ab3b2ba..73fcd9f83 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Flow.Launcher.Plugin.WindowsSettings.csproj @@ -9,6 +9,7 @@ prompt en-US enable + en @@ -68,4 +69,4 @@ - \ No newline at end of file + diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.it-IT.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.it-IT.resx index c0319e5fd..bcd3617ba 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.it-IT.resx +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.it-IT.resx @@ -837,7 +837,7 @@ Modalità chiara - Location + Posizione Area Privacy diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.pt-PT.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.pt-PT.resx index b243dafd8..45dc2c8cc 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.pt-PT.resx +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.pt-PT.resx @@ -2443,7 +2443,7 @@ Change search options for files and folders - Adjust settings before giving a presentation + Ajustar definições antes de uma apresentação Digitalizar um documento ou imagem @@ -2467,10 +2467,10 @@ Calibrate the screen for pen or touch input - Manage user certificates + Gerir certificados do utilizador - Schedule tasks + Agendar tarefas Ignore repeated keystrokes using FilterKeys diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.vi-VN.resx b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.vi-VN.resx new file mode 100644 index 000000000..851470744 --- /dev/null +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/Properties/Resources.vi-VN.resx @@ -0,0 +1,2515 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Giới thiệu + Area System + + + truy cập.cpl + File name, Should not translated + + + Tùy chọn khả năng tiếp cận + Area Control Panel (legacy settings) + + + Ứng dụng phụ kiện + Area Privacy + + + Truy cập cơ quan hoặc trường học + Area UserAccounts + + + Thông tin tài khoản + Area Privacy + + + Tài khoản + Area SurfaceHub + + + Trung tâm thông báo + Area Control Panel (legacy settings) + + + Kích hoạt + Area UpdateAndSecurity + + + Lịch sử hoạt động + Area Privacy + + + Thêm phần cứng + Area Control Panel (legacy settings) + + + Thêm chương trình xóa + Area Control Panel (legacy settings) + + + Thêm điện thoại của bạn + Area Phone + + + Các công cụ quản trị + Area System + + + Cài đặt hiển thị nâng cao + Area System, only available on devices that support advanced display options + + + Đồ họa nâng cao + + + ID quảng cáo + Area Privacy, Deprecated in Windows 10, version 1809 and later + + + Chế độ trên máy bay + Area NetworkAndInternet + + + Alt-Tab thay thế cho phép tìm kiếm thông qua các cửa sổ của bạn. + Means the key combination "Tabulator+Alt" on the keyboard + + + Tên khác + + + Hình ảnh động + + + Màu ứng dụng + + + Chẩn đoán DM + Area Privacy + + + Tính năng ứng dụng + Area Apps + + + Ứng dụng + Short/modern name for application + + + Ứng dụng và tính năng + Area Apps + + + Thiết lập hệ thống + Type of the setting is a "Modern Windows settings". We use the same term as used in start menu search at the moment. + + + Ứng dụng cho trang web + Area Apps + + + Âm lượng của từng ứng dụng và tùy chọn thiết bị + Area System, Added in Windows 10, version 1903 + + + appwiz.cpl + File name, Should not translated + + + Khu vực + Mean the settings area or settings category + + + Tài khoản + + + Các công cụ quản trị + Area Control Panel (legacy settings) + + + Ngoại hình và cá nhân hóa + + + Ứng dụng + + + Đồng hồ và khu vực + + + Bảng điều khiển + + + Cortana + + + Thiết bị + + + Truy cập nhanh + + + Phụ trợ + + + Trò chơi + + + Phần cứng và âm thanh + + + Trang chủ + + + Thực tế hỗn hợp + + + Mạng và Internet + + + Cá nhân hóa + + + Điện thoại + + + Quyền riêng tư + + + Chương trình + + + SurfaceHub + + + Hệ thống + + + Hệ thống và bảo mật + + + Thời gian và ngôn ngữ + + + Cập nhật và bảo mật + + + Tài khoản người sử dụng + + + Quyền truy cập được chỉ định + + + Âm thanh + Area EaseOfAccess + + + Cảnh báo âm thanh + + + Âm thanh và lời nói + Area MixedReality, only available if the Mixed Reality Portal app is installed. + + + Tải tập tin tự động + Area Privacy + + + Tự động phát + Area Device + + + Nền + Area Personalization + + + Ứng dụng nền + Area Privacy + + + Sao lưu + Area UpdateAndSecurity + + + Sao lưu và Khôi phục + Area Control Panel (legacy settings) + + + Trình tiết kiệm pin + Area System, only available on devices that have a battery, such as a tablet + + + Cài đặt tiết kiệm pin + Area System, only available on devices that have a battery, such as a tablet + + + Chi tiết sử dụng trình tiết kiệm pin + + + Sử dụng Pin + Area System, only available on devices that have a battery, such as a tablet + + + Thiết bị sinh trắc học + Area Control Panel (legacy settings) + + + Mã hóa ổ đĩa BitLocker + Area Control Panel (legacy settings) + + + Xanh dương (sáng) + + + Bluetooth + Area Device + + + Thiết bị Bluetooth + Area Control Panel (legacy settings) + + + Xanh lam – vàng + + + Bopomofo IME + Area TimeAndLanguage + + + bpmf + Should not translated + + + Đang phát sóng + Area Gaming + + + Lịch + Area Privacy + + + Lịch sử cuộc gọi + Area Privacy + + + đang gọi + + + Máy ảnh + Area Privacy + + + Cangjie IME + Area TimeAndLanguage + + + Caps Lock + Mean the "Caps Lock" key + + + Di động và SIM + Area NetworkAndInternet + + + Chọn thư mục nào xuất hiện trên Bắt đầu + Area Personalization + + + Dịch vụ khách hàng cho NetWare + Area Control Panel (legacy settings) + + + Nhận văn bản từ bảng nhớ tạm. + Area System + + + Phụ đề chi tiết + Area EaseOfAccess + + + Bộ lọc màu + Area EaseOfAccess + + + Quản lý màu sắc + Area Control Panel (legacy settings) + + + Màu + Area Personalization + + + Lệnh + The command to direct start a setting + + + Các thiết bị đã kết nối + Area Device + + + Danh bạ + Area Privacy + + + Bảng điều khiển + Type of the setting is a "(legacy) Control Panel setting" + + + Sao chép lệnh + + + Cách ly lõi + Means the protection of the system core + + + Cortana + Area Cortana + + + Cortana trên các thiết bị của tôi + Area Cortana + + + Cortana - Ngôn ngữ + Area Cortana + + + Người quản lý thông tin xác thực + Area Control Panel (legacy settings) + + + Thiết bị chéo + + + Thiết bị tùy chỉnh + + + Màu tối + + + Chế độ tối + + + Sử dụng dữ liệu + Area NetworkAndInternet + + + Ngày và giờ + Area TimeAndLanguage + + + Ứng dụng mặc định + Area Apps + + + Camera mặc định + Area Device + + + Vị trí mặc định + Area Control Panel (legacy settings) + + + Chương trình mặc định + Area Control Panel (legacy settings) + + + Vị trí mặc định + Area System + + + Delivery Optimization + Area UpdateAndSecurity + + + desk.cpl + File name, Should not translated + + + Desktop themes + Area Control Panel (legacy settings) + + + Mù màu Deuteranopia + Medical: Mean you don't can see red colors + + + Quản lý thiết bị + Area Control Panel (legacy settings) + + + Devices and printers + Area Control Panel (legacy settings) + + + DHCP + Should not translated + + + Dial-up + Area NetworkAndInternet + + + Truy cập thẳng + Area NetworkAndInternet, only available if DirectAccess is enabled + + + Direct open your phone + Area EaseOfAccess + + + Hiển thị + Area EaseOfAccess + + + Hiển thị thuộc tính + Area Control Panel (legacy settings) + + + DNS + Should not translated + + + Tài liệu + Area Privacy + + + Duplicating my display + Area System + + + During these hours + Area System + + + Ease of access center + Area Control Panel (legacy settings) + + + Bản in + Means the "Windows Edition" + + + Email + Area Privacy + + + Email and app accounts + Area UserAccounts + + + Mã hóa + Area System + + + Môi trường + Area MixedReality, only available if the Mixed Reality Portal app is installed. + + + Ethernet + Area NetworkAndInternet + + + Exploit Protection + + + Phụ trợ + Area Extra, , only used for setting of 3rd-Party tools + + + Eye control + Area EaseOfAccess + + + Eye tracker + Area Privacy, requires eyetracker hardware + + + Family and other people + Area UserAccounts + + + Feedback and diagnostics + Area Privacy + + + Tệp tin hệ thống + Area Privacy + + + FindFast + Area Control Panel (legacy settings) + + + findfast.cpl + File name, Should not translated + + + Tìm thiết bị + Area UpdateAndSecurity + + + Tường lửa + + + Focus assist - Quiet hours + Area System + + + Focus assist - Quiet moments + Area System + + + Tùy chọn thư mục + Area Control Panel (legacy settings) + + + Phông chữ + Area EaseOfAccess + + + Dành cho nhà phát triển + Area UpdateAndSecurity + + + Game bar + Area Gaming + + + Trình điều khiển trò chơi + Area Control Panel (legacy settings) + + + Game DVR + Area Gaming + + + Chế độ trò chơi + Area Gaming + + + Cổng + Should not translated + + + Tổng quan + Area Privacy + + + Get programs + Area Control Panel (legacy settings) + + + Bắt đầu + Area Control Panel (legacy settings) + + + Ánh Nhìn + Area Personalization, Deprecated in Windows 10, version 1809 and later + + + Cài đặt đồ họa + Area System + + + Thang độ xám + + + Green week + Mean you don't can see green colors + + + Headset display + Area MixedReality, only available if the Mixed Reality Portal app is installed. + + + Độ tương phản cao + Area EaseOfAccess + + + Holographic audio + + + Holographic Environment + + + Holographic Headset + + + Holographic Management + + + Home group + Area Control Panel (legacy settings) + + + Mã ID + MEans The "Windows Identifier" + + + Hình ảnh + + + Tùy chọn lập chỉ mục + Area Control Panel (legacy settings) + + + inetcpl.cpl + File name, Should not translated + + + Hồng ngoại + Area Control Panel (legacy settings) + + + Inking and typing + Area Privacy + + + Tùy chọn Internet + Area Control Panel (legacy settings) + + + intl.cpl + File name, Should not translated + + + Đảo ngược màu + + + IP + Should not translated + + + Isolated Browsing + + + Japan IME settings + Area TimeAndLanguage, available if the Microsoft Japan input method editor is installed + + + joy.cpl + File name, Should not translated + + + Joystick properties + Area Control Panel (legacy settings) + + + jpnime + Should not translated + + + Bàn phím + Area EaseOfAccess + + + Bàn phím số + + + Keys + + + Ngôn ngữ + Area TimeAndLanguage + + + Màu sắc đèn + + + Chế độ sáng + + + Vị trí + Area Privacy + + + Khoá màn hình + Area Personalization + + + Phóng + Area EaseOfAccess + + + Mail - Microsoft Exchange or Windows Messaging + Area Control Panel (legacy settings) + + + main.cpl + File name, Should not translated + + + Manage known networks + Area NetworkAndInternet + + + Manage optional features + Area Apps + + + Tin nhắn + Area Privacy + + + Metered connection + + + Micro điện thoại + Area Privacy + + + Microsoft Mail Post Office + Area Control Panel (legacy settings) + + + mlcfg32.cpl + File name, Should not translated + + + mmsys.cpl + File name, Should not translated + + + Thiết bị di động + + + Điểm phát sóng di động + Area NetworkAndInternet + + + modem.cpl + File name, Should not translated + + + Đơn âm + + + Xem chi tiết + Area Cortana + + + Chuyển động + Area Privacy + + + Chuột + Area EaseOfAccess + + + Mouse and touchpad + Area Device + + + Mouse, Fonts, Keyboard, and Printers properties + Area Control Panel (legacy settings) + + + Con trỏ chuột + Area EaseOfAccess + + + Multimedia properties + Area Control Panel (legacy settings) + + + Đa nhiệm + Area System + + + Người dẫn chuyện + Area EaseOfAccess + + + Thanh điều hướng + Area Personalization + + + netcpl.cpl + File name, Should not translated + + + netsetup.cpl + File name, Should not translated + + + Mạng + Area NetworkAndInternet + + + Network and sharing center + Area Control Panel (legacy settings) + + + Kết nối mạng + Area Control Panel (legacy settings) + + + Network properties + Area Control Panel (legacy settings) + + + Network Setup Wizard + Area Control Panel (legacy settings) + + + Trạng thái mạng + Area NetworkAndInternet + + + NFC + Area NetworkAndInternet + + + NFC Transactions + "NFC should not translated" + + + Chế độ đêm + + + Night light settings + Area System + + + Ghi chú + + + Chỉ khả dụng khi bạn đã kết nối thiết bị di động với thiết bị của mình. + + + Chỉ khả dụng trên các thiết bị hỗ trợ tùy chọn đồ họa nâng cao. + + + Chỉ khả dụng trên các thiết bị có pin, chẳng hạn như máy tính bảng. + + + Không được dùng nữa trong Windows 10, phiên bản 1809 (bản dựng 17763) trở lên. + + + Only available if Dial is paired. + + + Only available if DirectAccess is enabled. + + + Chỉ khả dụng trên các thiết bị hỗ trợ tùy chọn đồ họa nâng cao. + + + Only present if user is enrolled in WIP. + + + Requires eyetracker hardware. + + + Available if the Microsoft Japan input method editor is installed. + + + Available if the Microsoft Pinyin input method editor is installed. + + + Available if the Microsoft Wubi input method editor is installed. + + + Only available if the Mixed Reality Portal app is installed. + + + Only available on mobile and if the enterprise has deployed a provisioning package. + + + Added in Windows 10, version 1903 (build 18362). + + + Added in Windows 10, version 2004 (build 19041). + + + Only available if "settings apps" are installed, for example, by a 3rd party. + + + Only available if touchpad hardware is present. + + + Only available if the device has a Wi-Fi adapter. + + + Device must be Windows Anywhere-capable. + + + Only available if enterprise has deployed a provisioning package. + + + Thông báo + Area Privacy + + + Hành động thông báo + Area System + + + Num Lock + Mean the "Num Lock" key + + + nwc.cpl + File name, Should not translated + + + odbccp32.cpl + File name, Should not translated + + + ODBC Data Source Administrator (32-bit) + Area Control Panel (legacy settings) + + + ODBC Data Source Administrator (64-bit) + Area Control Panel (legacy settings) + + + Offline files + Area Control Panel (legacy settings) + + + Bàn đồ ngoại tuyến + Area Apps + + + Offline Maps - Download maps + Area Apps + + + On-Screen + + + Hệ điều hành + Means the "Operating System" + + + thiết bị khác + Area Privacy + + + Tùy chọn khác + Area EaseOfAccess + + + Người dùng khác + + + Kiểm soát phụ huynh + Area Control Panel (legacy settings) + + + Mật khẩu + + + password.cpl + File name, Should not translated + + + Password properties + Area Control Panel (legacy settings) + + + Pen and input devices + Area Control Panel (legacy settings) + + + Pen and touch + Area Control Panel (legacy settings) + + + Pen and Windows Ink + Area Device + + + Ghép đôi người xung quanh + Area Control Panel (legacy settings) + + + Performance information and tools + Area Control Panel (legacy settings) + + + Permissions and history + Area Cortana + + + Personalization (category) + Area Personalization + + + Điện thoại + Area Phone + + + Phone and modem + Area Control Panel (legacy settings) + + + Phone and modem - Options + Area Control Panel (legacy settings) + + + Phone calls + Area Privacy + + + Phone - Default apps + Area System + + + Picture + + + Hình ảnh + Area Privacy + + + Pinyin IME settings + Area TimeAndLanguage, available if the Microsoft Pinyin input method editor is installed + + + Pinyin IME settings - domain lexicon + Area TimeAndLanguage + + + Pinyin IME settings - Key configuration + Area TimeAndLanguage + + + Pinyin IME settings - UDP + Area TimeAndLanguage + + + Playing a game full screen + Area Gaming + + + Plugin to search for Windows settings + + + Windows Settings + + + Power and sleep + Area System + + + powercfg.cpl + File name, Should not translated + + + Tùy chọn chế độ Nguồn + Area Control Panel (legacy settings) + + + Trình bày + + + Máy in + Area Control Panel (legacy settings) + + + Printers and scanners + Area Device + + + chụp màn hình + Mean the "Print screen" key + + + Problem reports and solutions + Area Control Panel (legacy settings) + + + Vi xử lý + + + Programs and features + Area Control Panel (legacy settings) + + + Projecting to this PC + Area System + + + Mù màu Protanopia + Medical: Mean you don't can see green colors + + + Đang cấp phép + Area UserAccounts, only available if enterprise has deployed a provisioning package + + + Cảm ứng tiệm cận + Area NetworkAndInternet + + + Proxy + Area NetworkAndInternet + + + Quickime + Area TimeAndLanguage + + + Quiet moments game + + + Radio + Area Privacy + + + RAM + Means the Read-Access-Memory (typical the used to inform about the size) + + + Sự công nhận + + + Phục Hồi + Area UpdateAndSecurity + + + Red eye + Mean red eye effect by over-the-night flights + + + Đỏ – xanh lục + Mean the weakness you can't differ between red and green colors + + + Red week + Mean you don't can see red colors + + + Khu vực + Area TimeAndLanguage + + + Khu vực, ngôn ngữ + Area TimeAndLanguage + + + Regional settings properties + Area Control Panel (legacy settings) + + + Khu vực, ngôn ngữ + Area Control Panel (legacy settings) + + + Region formatting + + + RemoteApp and desktop connections + Area Control Panel (legacy settings) + + + VNC + Area System + + + Scanners and cameras + Area Control Panel (legacy settings) + + + schedtasks + File name, Should not translated + + + Lịch trình + + + Hoạt động theo lịch trình + Area Control Panel (legacy settings) + + + Xoay màn hình + Area System + + + Thanh cuộn + + + Khóa cuộn + Mean the "Scroll Lock" key + + + SDNS + Should not translated + + + Searching Windows + Area Cortana + + + SecureDNS + Should not translated + + + Security Center + Area Control Panel (legacy settings) + + + Security Processor + + + Session cleanup + Area SurfaceHub + + + Settings home page + Area Home, Overview-page for all areas of settings + + + Set up a kiosk + Area UserAccounts + + + Shared experiences + Area System + + + Shortcuts + + + wifi + dont translate this, is a short term to find entries + + + Sign-in options + Area UserAccounts + + + Sign-in options - Dynamic lock + Area UserAccounts + + + Kích thước + Size for text and symbols + + + Âm thanh + Area System + + + Nói + + Area EaseOfAccess + + + Nhận dạng tiếng nói + Area Control Panel (legacy settings) + + + Speech typing + + + Start + Area Personalization + + + Start places + + + Startup apps + Area Apps + + + sticpl.cpl + File name, Should not translated + + + Storage + Area System + + + Storage policies + Area System + + + Storage Sense + Area System + + + in + Example: Area "System" in System settings + + + Sync center + Area Control Panel (legacy settings) + + + Sync your settings + Area UserAccounts + + + sysdm.cpl + File name, Should not translated + + + Hệ thống + Area Control Panel (legacy settings) + + + System properties and Add New Hardware wizard + Area Control Panel (legacy settings) + + + Tab + Means the key "Tabulator" on the keyboard + + + Tablet mode + Area System + + + Tablet PC settings + Area Control Panel (legacy settings) + + + Talk + + + Talk to Cortana + Area Cortana + + + Thanh tác vụ + Area Personalization + + + Taskbar color + + + Công việc + Area Privacy + + + Web Conferencing + Area SurfaceHub + + + Team device management + Area SurfaceHub + + + Văn bản thành giọng nói + Area Control Panel (legacy settings) + + + Giao diện + Area Personalization + + + themes.cpl + File name, Should not translated + + + timedate.cpl + File name, Should not translated + + + Dòng thời gian + + + Chạm + + + Phản hồi khi chạm + + + Touchpad + Area Device + + + Minh bạch + + + Mù màu lam + Medical: Mean you don't can see yellow and blue colors + + + Khắc phục sự cố + Area UpdateAndSecurity + + + TruePlay + Area Gaming + + + Đang nhập + Area Device + + + Gỡ cài đặt + Area MixedReality, only available if the Mixed Reality Portal app is installed. + + + USB + Area Device + + + Tài khoản người sử dụng + Area Control Panel (legacy settings) + + + Phiên bản + Means The "Windows Version" + + + Phát lại video + Area Apps + + + Video + Area Privacy + + + Máy ảo + + + Virus + Means the virus in computers and software + + + giọng nói "gây nghiện" + Area Privacy + + + Âm lượng + + + VPN + Area NetworkAndInternet + + + Hình nền + + + Warmer color + + + Welcome center + Area Control Panel (legacy settings) + + + Màn hình chào mừng + Area SurfaceHub + + + wgpocpl.cpl + File name, Should not translated + + + Bánh xe + Area Device + + + Wifi + Area NetworkAndInternet, only available if Wi-Fi calling is enabled + + + Gọi qua Wi-Fi + Area NetworkAndInternet, only available if Wi-Fi calling is enabled + + + Cài đặt WiFi + "Wi-Fi" should not translated + + + chế độ cửa sổ + + + Windows Anytime Upgrade + Area Control Panel (legacy settings) + + + Windows Anywhere + Area UserAccounts, device must be Windows Anywhere-capable + + + Windows CardSpace + Area Control Panel (legacy settings) + + + Windows Defender + Area Control Panel (legacy settings) + + + Windows Firewall + Area Control Panel (legacy settings) + + + Windows Hello setup - Face + Area UserAccounts + + + Windows Hello setup - Fingerprint + Area UserAccounts + + + Windows Insider Program + Area UpdateAndSecurity + + + Windows Mobility Center + Area Control Panel (legacy settings) + + + Windows search + Area Cortana + + + Windows Security + Area UpdateAndSecurity + + + Windows Update + Area UpdateAndSecurity + + + Windows Update - Advanced options + Area UpdateAndSecurity + + + Windows Update - Check for updates + Area UpdateAndSecurity + + + Windows Update - Restart options + Area UpdateAndSecurity + + + Windows Update - View optional updates + Area UpdateAndSecurity + + + Windows Update - View update history + Area UpdateAndSecurity + + + Wireless + + + Workplace + + + Workplace provisioning + Area UserAccounts + + + Wubi IME settings + Area TimeAndLanguage, available if the Microsoft Wubi input method editor is installed + + + Wubi IME settings - UDP + Area TimeAndLanguage + + + Xbox Networking + Area Gaming + + + Your info + Area UserAccounts + + + Zoom + Mean zooming of things via a magnifier + + + Change device installation settings + + + Turn off background images + + + Navigation properties + + + Media streaming options + + + Make a file type always open in a specific program + + + Change the Narrator’s voice + + + Find and fix keyboard problems + + + Use screen reader + + + Show which workgroup this computer is on + + + Change mouse wheel settings + + + Manage computer certificates + + + Find and fix problems + + + Change settings for content received using Tap and send + + + Change default settings for media or devices + + + Print the speech reference card + + + Calibrate display colour + + + Manage file encryption certificates + + + View recent messages about your computer + + + Give other users access to this computer + + + Show hidden files and folders + + + Change Windows To Go start-up options + + + See which processes start up automatically when you start Windows + + + Tell if an RSS feed is available on a website + + + Add clocks for different time zones + + + Add a Bluetooth device + + + Customise the mouse buttons + + + Set tablet buttons to perform certain tasks + + + View installed fonts + + + Change the way currency is displayed + + + Edit group policy + + + Manage browser add-ons + + + Check processor speed + + + Check firewall status + + + Send or receive a file + + + Add or remove user accounts + + + Edit the system environment variables + + + Manage BitLocker + + + Auto-hide the taskbar + + + Change sound card settings + + + Make changes to accounts + + + Edit local users and groups + + + View network computers and devices + + + Install a program from the network + + + View scanners and cameras + + + Microsoft IME Register Word (Japanese) + + + Restore your files with File History + + + Turn On-Screen keyboard on or off + + + Block or allow third-party cookies + + + Find and fix audio recording problems + + + Create a recovery drive + + + Microsoft New Phonetic Settings + + + Generate a system health report + + + Fix problems with your computer + + + Back up and Restore (Windows 7) + + + Preview, delete, show or hide fonts + + + Microsoft Quick Settings + + + View reliability history + + + Access RemoteApp and desktops + + + Set up ODBC data sources + + + Reset Security Policies + + + Block or allow pop-ups + + + Turn autocomplete in Internet Explorer on or off + + + Microsoft Pinyin SimpleFast Options + + + Change what closing the lid does + + + Turn off unnecessary animations + + + Create a restore point + + + Turn off automatic window arrangement + + + Troubleshooting History + + + Diagnose your computer's memory problems + + + View recommended actions to keep Windows running smoothly + + + Change cursor blink rate + + + Add or remove programs + + + Create a password reset disk + + + Configure advanced user profile properties + + + Start or stop using AutoPlay for all media and devices + + + Change Automatic Maintenance settings + + + Specify single- or double-click to open + + + Select users who can use remote desktop + + + Show which programs are installed on your computer + + + Allow remote access to your computer + + + View advanced system settings + + + How to install a program + + + Change how your keyboard works + + + Automatically adjust for daylight saving time + + + Change the order of Windows SideShow gadgets + + + Check keyboard status + + + Control the computer without the mouse or keyboard + + + Change or remove a program + + + Change multi-touch gesture settings + + + Set up ODBC data sources (64-bit) + + + Configure proxy server + + + Change your homepage + + + Group similar windows on the taskbar + + + Change Windows SideShow settings + + + Use audio description for video + + + Change workgroup name + + + Find and fix printing problems + + + Change when the computer sleeps + + + Set up a virtual private network (VPN) connection + + + Accommodate learning abilities + + + Set up a dial-up connection + + + Set up a connection or network + + + How to change your Windows password + + + Make it easier to see the mouse pointer + + + Set up iSCSI initiator + + + Accommodate low vision + + + Manage offline files + + + Review your computer's status and resolve issues + + + Microsoft ChangJie Settings + + + Replace sounds with visual cues + + + Change temporary Internet file settings + + + Connect to the Internet + + + Find and fix audio playback problems + + + Change the mouse pointer display or speed + + + Back up your recovery key + + + Save backup copies of your files with File History + + + View current accessibility settings + + + Change tablet pen settings + + + Change how your mouse works + + + Show how much RAM is on this computer + + + Edit power plan + + + Adjust system volume + + + Defragment and optimise your drives + + + Set up ODBC data sources (32-bit) + + + Change Font Settings + + + Magnify portions of the screen using Magnifier + + + Change the file type associated with a file extension + + + View event logs + + + Manage Windows Credentials + + + Set up a microphone + + + Change how the mouse pointer looks + + + Change power-saving settings + + + Optimise for blindness + + + + + + + Turn Windows features on or off + + + Show which operating system your computer is running + + + View local services + + + Manage Work Folders + + + Encrypt your offline files + + + Train the computer to recognise your voice + + + Advanced printer setup + + + Change default printer + + + Edit environment variables for your account + + + Optimise visual display + + + Change mouse click settings + + + Change advanced colour management settings for displays, scanners and printers + + + Let Windows suggest Ease of Access settings + + + Clear disk space by deleting unnecessary files + + + View devices and printers + + + Private Character Editor + + + Record steps to reproduce a problem + + + Adjust the appearance and performance of Windows + + + Settings for Microsoft IME (Japanese) + + + Invite someone to connect to your PC and help you, or offer to help someone else + + + Run programs made for previous versions of Windows + + + Choose the order of how your screen rotates + + + Change how Windows searches + + + Set flicks to perform certain tasks + + + Change account type + + + Change screen saver + + + Change User Account Control settings + + + Turn on easy access keys + + + Identify and repair network problems + + + Find and fix networking and connection problems + + + Play CDs or other media automatically + + + View basic information about your computer + + + Choose how you open links + + + Allow Remote Assistance invitations to be sent from this computer + + + Task Manager + + + Turn flicks on or off + + + Add a language + + + View network status and tasks + + + Turn Magnifier on or off + + + See the name of this computer + + + View network connections + + + Perform recommended maintenance tasks automatically + + + Manage disk space used by your offline files + + + Turn High Contrast on or off + + + Change the way time is displayed + + + Change how web pages are displayed in tabs + + + Change the way dates and lists are displayed + + + Manage audio devices + + + Change security settings + + + Check security status + + + Delete cookies or temporary files + + + Specify which hand you write with + + + Change touch input settings + + + How to change the size of virtual memory + + + Hear text read aloud with Narrator + + + Set up USB game controllers + + + Show which domain your computer is on + + + View all problem reports + + + 16-Bit Application Support + + + Set up dialling rules + + + Enable or disable session cookies + + + Give administrative rights to a domain user + + + Choose when to turn off display + + + Move the pointer with the keypad using MouseKeys + + + Change Windows SideShow-compatible device settings + + + Adjust commonly used mobility settings + + + Change text-to-speech settings + + + Set the time and date + + + Change location settings + + + Change mouse settings + + + Manage Storage Spaces + + + Show or hide file extensions + + + Allow an app through Windows Firewall + + + Change system sounds + + + Adjust ClearType text + + + Turn screen saver on or off + + + Find and fix windows update problems + + + Change Bluetooth settings + + + Connect to a network + + + Change the search provider in Internet Explorer + + + Join a domain + + + Add a device + + + Find and fix problems with Windows Search + + + Choose a power plan + + + Change how the mouse pointer looks when it’s moving + + + Uninstall a program + + + Create and format hard disk partitions + + + Change date, time or number formats + + + Change PC wake-up settings + + + Manage network passwords + + + Change input methods + + + Manage advanced sharing settings + + + Change battery settings + + + Rename this computer + + + Lock or unlock the taskbar + + + Manage Web Credentials + + + Change the time zone + + + Start speech recognition + + + View installed updates + + + What's happened to the Quick Launch toolbar? + + + Change search options for files and folders + + + Adjust settings before giving a presentation + + + Scan a document or picture + + + Change the way measurements are displayed + + + Press key combinations one at a time + + + Khôi phục dữ liệu, tập tin hoặc máy tính từ bản sao lưu (Windows 7) + + + Set your default programs + + + Set up a broadband connection + + + Calibrate the screen for pen or touch input + + + Manage user certificates + + + Schedule tasks + + + Ignore repeated keystrokes using FilterKeys + + + Find and fix bluescreen problems + + + Hear a tone when keys are pressed + + + Delete browsing history + + + Change what the power buttons do + + + Create standard user account + + + Take speech tutorials + + + View system resource usage in Task Manager + + + Create an account + + + Get more features with a new edition of Windows + + + Bảng điều khiển + + + TaskLink + + + Unknown + + \ No newline at end of file diff --git a/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json b/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json index 5e82a43c0..dc8bfa22c 100644 --- a/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json +++ b/Plugins/Flow.Launcher.Plugin.WindowsSettings/plugin.json @@ -4,7 +4,7 @@ "Description": "Search settings inside Control Panel and Settings App", "Name": "Windows Settings", "Author": "TobiasSekan", - "Version": "4.0.6", + "Version": "4.0.7", "Language": "csharp", "Website": "https://github.com/Flow-Launcher/Flow.Launcher", "ExecuteFileName": "Flow.Launcher.Plugin.WindowsSettings.dll", diff --git a/README.md b/README.md index 9ebbe246c..866cdf8b3 100644 --- a/README.md +++ b/README.md @@ -16,77 +16,15 @@

-Dedicated to making your work flow more seamless. Search everything from applications, files, bookmarks, YouTube, Twitter and more. Flow will continue to evolve, designed to be open and built with the community at heart. +A quick file search and app launcher for Windows with community-made plugins.

+ +

+Dedicated to making your work flow more seamless. Search everything from applications, files, bookmarks, YouTube, Twitter and more. Flow will continue to evolve, designed to be open and built with the community at heart.

Remember to star it, flow will love you more :)

-## 🎅 New Features🤶 - -### Preview Panel - - - -- Use the F1 key to open/hide the preview panel. -- Media files will be displayed as large images, otherwise a large icon and entire path will be displayed. -- Turn on preview permanently via Settings (Always Preview). -- Use hotkeys (Ctrl+Plus,Minus / Ctrl+],[) to adjust flow's search window width and height quickly if the preview area is too narrow. -- This feature is currently in its early stages. - -### Everything Plugin Merged Into Explorer - - - -- Switch easily between Everything and Windows Search to take advantage of both search engines (remember to remove existing Everything plugin). -- Use features available to both Everything and Explorer plugins - -### Date & Time Display In Search Window - - - -- Display the date and time when the search window is triggered. - -### Drag & Drop - - - -- Drag an item to Discord or computer location. -- The target program determines whether the drop is to copy or move the item (can change via CTRL or Alt), and the operation is displayed on the mouse cursor. - -### Custom Shortcut - - - - -- New shortcut functionality to set additional action keywords or search terms. - -### Improved Program Plugin - -- PATH is now indexed -- Support for .url files, flow can now search installed steam/epic games. -- Improved UWP indexing. - -### Improved Memory Usage - -- Fixed a memory leak and reduced overall memory usage. - -### Improved Plugin / Plugin Store - -- Search plugins in the Plugin Store and existing plugin tab. -- Categorised sections in Plugin Store to easily see new and updated plugins. - -### Improved Non-C# Plugin's Panel Design - - - -- The design has been adjusted to align to the overall look and feel of flow. -- Simplified the information displayed on buttons - -🚂[Full Changelogs](https://github.com/Flow-Launcher/Flow.Launcher/releases) - - -

Getting StartedFeatures • @@ -104,15 +42,29 @@ Dedicated to making your work flow more seamless. Search everything from applica ### Installation -| [Windows 7+ installer](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest/download/Flow-Launcher-Setup.exe) | [Portable](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest/download/Flow-Launcher-Portable.zip) | -| :----------------------------------------------------------: | :----------------------------------------------------------: | +[Windows 7+ Installer](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest/download/Flow-Launcher-Setup.exe) or [Portable Version](https://github.com/Flow-Launcher/Flow.Launcher/releases/latest/download/Flow-Launcher-Portable.zip) -| `winget install "Flow Launcher"` | `scoop install Flow-Launcher` | `choco install Flow-Launcher` | -| :------------------------------: | :------------------------------: | :------------------------------: | +#### Winget + +``` +winget install "Flow Launcher" +``` + +#### Scoop + +``` +scoop install Flow-Launcher +``` + +#### Chocolatey + +``` +choco install Flow-Launcher +``` > When installing for the first time Windows may raise an issue about security due to code not being signed, if you downloaded from this repo then you are good to continue the set up. -And you can download [early access version](https://github.com/Flow-Launcher/Prereleases/releases). +Or download the [early access version](https://github.com/Flow-Launcher/Prereleases/releases). @@ -123,6 +75,7 @@ And you can download [early access version](https://github.com/Flow-Launcher/Pre - Search for apps, files or file contents. +- Supports Everything and Windows Index. @@ -156,7 +109,7 @@ And you can download [early access version](https://github.com/Flow-Launcher/Pre - Run batch and PowerShell commands as Administrator or a different user. -- Ctrl+Enter to Run as Administrator. +- Ctrl+Shift+Enter to Run as Administrator. ### Explorer @@ -164,6 +117,13 @@ And you can download [early access version](https://github.com/Flow-Launcher/Pre - Save file or folder locations for quick access. +#### Drag & Drop + + + +- Drag a file/folder to File Explorer, or even Discord. +- Copy/move behavior can be change via Ctrl or Shift, and the operation is displayed on the mouse cursor. + ### Windows & Control Panel Settings @@ -176,6 +136,16 @@ And you can download [early access version](https://github.com/Flow-Launcher/Pre - Prioritise the order of each plugin's results. +### Preview Panel + + + +- Use F1 to toggle the preview panel. +- Media files will be displayed as large images, otherwise a large icon and full path will be displayed. +- Turn on preview permanently via Settings (Always Preview). +- Use Ctrl++/- and Ctrl+[/] to adjust search window width and height quickly if the preview area is too narrow. + + ### Customizations @@ -187,12 +157,48 @@ And you can download [early access version](https://github.com/Flow-Launcher/Pre - There are various themes and you also can make your own. -### 💬 Language +#### Date & Time Display In Search Window + + + +- Display date and time in search window. + +### 💬 Languages - Supports languages from Chinese to Italian and more. -- Supports Pinyin search. +- Supports Pinyin (拼音) search. - [Crowdin](https://crowdin.com/project/flow-launcher) support for language translations. +
+Supported languages +
    +
  • English
  • +
  • 中文
  • +
  • 中文(繁体)
  • +
  • Українська
  • +
  • Русский
  • +
  • Français
  • +
  • 日本語
  • +
  • Dutch
  • +
  • Polski
  • +
  • Dansk
  • +
  • de, Deutsch
  • +
  • ko, 한국어
  • +
  • Srpski
  • +
  • Português
  • +
  • Português (Brasil)
  • +
  • Spanish
  • +
  • es-419, Spanish (Latin America)
  • +
  • Italiano
  • +
  • Norsk Bokmål
  • +
  • Slovenčina
  • +
  • Türkçe
  • +
  • čeština
  • +
  • اللغة العربية
  • +
  • Tiếng Việt
  • +
+
+ ### Portable - Fully portable. @@ -206,7 +212,7 @@ And you can download [early access version](https://github.com/Flow-Launcher/Pre - Pause hotkey activation when you are playing games. -- When in search window use Ctrl+F12 to toggle on/off. +- When in search window use Ctrl+F12 to toggle on/off. - Type `Toggle Game Mode` @@ -257,6 +263,7 @@ And you can download [early access version](https://github.com/Flow-Launcher/Pre ### 🛒 Plugin Store + - You can view the full plugin list or quickly install a plugin via the Plugin Store menu inside Settings @@ -267,26 +274,29 @@ And you can download [early access version](https://github.com/Flow-Launcher/Pre ## ⌨️ Hotkeys -| Hotkey | Description | -| ------------------------------------------------------------------ | ---------------------------------------------- | -| Alt+ Space | Open search window (default and configurable) | -| Enter | Execute | -| Ctrl+Shift+Enter | Run as admin | -| | Scroll up & down | -| | Back to result / Open Context Menu | -| Ctrl +O , Shift +Enter | Open Context Menu | -| Tab | Autocomplete | -| F1 | Toggle Preview Panel (default and configurable)| -| Esc | Back to results / hide search window | -| Ctrl +C | Copy the actual folder / file | -| Ctrl +I | Open flow's settings | -| Ctrl +R | Run the current query again (refresh results) | -| F5 | Reload all plugin data | -| Ctrl + F12 | Toggle Game Mode when in search window | -| Ctrl + +,- | Quickly change maximum results shown | -| Ctrl + [,] | Quickly change search window width | -| Ctrl + H | Open search history | -| Ctrl + Backspace | Back to previous directory | +| Hotkey | Description | +| ------------------------------------------------------------------------- | ----------------------------------------------- | +| Alt+Space | Open search window (default and configurable) | +| Enter | Execute | +| Ctrl+Enter | Open containing folder | +| Ctrl+Shift+Enter | Run as admin | +| /, Shift+Tab/Tab | Previous / Next result | +| / | Back to result / Open Context Menu | +| Ctrl+O , Shift+Enter | Open Context Menu | +| Ctrl+Tab | Autocomplete | +| F1 | Toggle Preview Panel (default and configurable) | +| Esc | Back to results / hide search window | +| Ctrl+C | Copy folder / file | +| Ctrl+Shift+C | Copy folder / file path | +| Ctrl+I | Open Flow's settings | +| Ctrl+R | Run the current query again (refresh results) | +| F5 | Reload all plugin data | +| Ctrl+F12 | Toggle Game Mode when in search window | +| Ctrl++,- | Adjust maximum results shown | +| Ctrl+[,] | Adjust search window width | +| Ctrl+H | Open search history | +| Ctrl+Backspace | Back to previous directory | +| PageUp/PageDown | Previous / Next Page | ## System Command List @@ -313,8 +323,6 @@ And you can download [early access version](https://github.com/Flow-Launcher/Pre | Flow Launcher UserData Folder | Open the location where Flow Launcher's settings are stored | | Toggle Game Mode | Toggle Game Mode | - - ### 💁‍♂️ Tips - [More tips](https://flowlauncher.com/docs/#/usage-tips) diff --git a/Settings.XamlStyler b/Settings.XamlStyler new file mode 100644 index 000000000..7ea0744f1 --- /dev/null +++ b/Settings.XamlStyler @@ -0,0 +1,42 @@ +{ + "AttributesTolerance": 2, + "KeepFirstAttributeOnSameLine": false, + "MaxAttributeCharactersPerLine": 0, + "MaxAttributesPerLine": 1, + "NewlineExemptionElements": "RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter", + "SeparateByGroups": false, + "AttributeIndentation": 0, + "AttributeIndentationStyle": 1, + "RemoveDesignTimeReferences": false, + "IgnoreDesignTimeReferencePrefix": false, + "EnableAttributeReordering": true, + "AttributeOrderingRuleGroups": [ + "x:Class", + "xmlns, xmlns:x", + "xmlns:*", + "x:Key, Key, x:Name, Name, x:Uid, Uid, Title", + "Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom", + "Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight", + "Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex", + "*:*, *", + "PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint", + "mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText", + "Storyboard.*, From, To, Duration" + ], + "FirstLineAttributes": "", + "OrderAttributesByName": true, + "PutEndingBracketOnNewLine": false, + "RemoveEndingTagOfEmptyElement": true, + "SpaceBeforeClosingSlash": true, + "RootElementLineBreakRule": 0, + "ReorderVSM": 2, + "ReorderGridChildren": false, + "ReorderCanvasChildren": false, + "ReorderSetters": 0, + "FormatMarkupExtension": true, + "NoNewLineMarkupExtensions": "x:Bind, Binding", + "ThicknessSeparator": 1, + "ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin, CornerRadius", + "FormatOnSave": true, + "CommentPadding": 2, +} diff --git a/appveyor.yml b/appveyor.yml index b84230c43..2503abce3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: '1.17.2.{build}' +version: '1.18.0.{build}' init: - ps: | @@ -51,7 +51,7 @@ deploy: - provider: NuGet artifact: Plugin nupkg api_key: - secure: Uho7u3gk4RHzyWGgqgXZuOeI55NbqHLQ9tXahL7xmE4av2oiSldrNiyGgy/0AQYw + secure: sCSd5JWgdzJWDa9kpqECut5ACPKZqcoxKU8ERKC00k7VIjig3/+nFV5zzTcGb0w3 on: APPVEYOR_REPO_TAG: true