mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge branch 'dev' into merge_2_1_0
This commit is contained in:
commit
980c1a1060
17 changed files with 532 additions and 98 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<Project>
|
||||
<PropertyGroup>
|
||||
<AccelerateBuildsInVisualStudio>true</AccelerateBuildsInVisualStudio>
|
||||
<!-- Work around https://github.com/dotnet/runtime/issues/109682 -->
|
||||
<!-- Workaround https://github.com/dotnet/runtime/issues/109682 -->
|
||||
<CETCompat>false</CETCompat>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
@ -7,6 +7,7 @@ using CommunityToolkit.Mvvm.DependencyInjection;
|
|||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.Logger;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Localization.Attributes;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Plugin.SharedModels;
|
||||
|
||||
|
|
@ -514,6 +515,21 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public LastQueryMode LastQueryMode { get; set; } = LastQueryMode.Selected;
|
||||
|
||||
private HistoryStyle _historyStyle = HistoryStyle.Query;
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public HistoryStyle HistoryStyle
|
||||
{
|
||||
get => _historyStyle;
|
||||
set
|
||||
{
|
||||
if (_historyStyle != value)
|
||||
{
|
||||
_historyStyle = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||
public AnimationSpeeds AnimationSpeed { get; set; } = AnimationSpeeds.Medium;
|
||||
public int CustomAnimationLength { get; set; } = 360;
|
||||
|
|
@ -696,4 +712,14 @@ namespace Flow.Launcher.Infrastructure.UserSettings
|
|||
FullPathOpen,
|
||||
Directory
|
||||
}
|
||||
|
||||
[EnumLocalize]
|
||||
public enum HistoryStyle
|
||||
{
|
||||
[EnumLocalizeKey(nameof(Localize.queryHistory))]
|
||||
Query,
|
||||
|
||||
[EnumLocalizeKey(nameof(Localize.executedHistory))]
|
||||
LastOpened
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ using System.IO;
|
|||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Flow.Launcher.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes a result of a <see cref="Query"/> executed by a plugin
|
||||
/// Describes a result of a <see cref="Query"/> executed by a plugin.
|
||||
/// This or its child classes is serializable.
|
||||
/// </summary>
|
||||
public class Result
|
||||
{
|
||||
|
|
@ -21,6 +23,8 @@ namespace Flow.Launcher.Plugin
|
|||
|
||||
private string _icoPath;
|
||||
|
||||
private string _icoPathAbsolute;
|
||||
|
||||
private string _copyText = string.Empty;
|
||||
|
||||
private string _badgeIcoPath;
|
||||
|
|
@ -64,15 +68,27 @@ namespace Flow.Launcher.Plugin
|
|||
public string AutoCompleteText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The image to be displayed for the result.
|
||||
/// Path or URI to the icon image for this result.
|
||||
/// Updates <see cref="IcoPathAbsolute"/> appropriately when set.
|
||||
/// </summary>
|
||||
/// <value>Can be a local file path or a URL.</value>
|
||||
/// <remarks>GlyphInfo is prioritized if not null</remarks>
|
||||
/// <remarks>
|
||||
/// Preferred usage: provide a path relative to the plugin directory (for example: "Images\icon.png").
|
||||
/// Because <see cref="IcoPath"/> is serialized, using relative paths keeps the icon reference portable
|
||||
/// when Flow is moved.
|
||||
///
|
||||
/// Accepted formats:
|
||||
/// - Relative file paths (resolved against <see cref="PluginDirectory"/> into <see cref="IcoPathAbsolute"/>)
|
||||
/// - Absolute file paths (left as-is)
|
||||
/// - HTTP/HTTPS URLs (left as-is)
|
||||
/// - Data URIs (left as-is)
|
||||
/// </remarks>
|
||||
public string IcoPath
|
||||
{
|
||||
get => _icoPath;
|
||||
set
|
||||
{
|
||||
_icoPath = value;
|
||||
|
||||
// As a standard this property will handle prepping and converting to absolute local path for icon image processing
|
||||
if (!string.IsNullOrEmpty(value)
|
||||
&& !string.IsNullOrEmpty(PluginDirectory)
|
||||
|
|
@ -81,15 +97,23 @@ namespace Flow.Launcher.Plugin
|
|||
&& !value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)
|
||||
&& !value.StartsWith("data:image", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_icoPath = Path.Combine(PluginDirectory, value);
|
||||
_icoPathAbsolute = Path.Combine(PluginDirectory, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_icoPath = value;
|
||||
_icoPathAbsolute = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Absolute path or URI which is used to load and display the result icon for Flow.
|
||||
/// This is populated by the <see cref="IcoPath"/> setter.
|
||||
/// If a relative path was provided to <see cref="IcoPath"/>, this property will contain the resolved
|
||||
/// absolute local path after combining with <see cref="PluginDirectory"/>.
|
||||
/// </summary>
|
||||
public string IcoPathAbsolute => _icoPathAbsolute;
|
||||
|
||||
/// <summary>
|
||||
/// The image to be displayed for the badge of the result.
|
||||
/// </summary>
|
||||
|
|
@ -131,17 +155,34 @@ namespace Flow.Launcher.Plugin
|
|||
/// <summary>
|
||||
/// Delegate to load an icon for this result.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public IconDelegate Icon = null;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to load an icon for the badge of this result.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public IconDelegate BadgeIcon = null;
|
||||
|
||||
private GlyphInfo _glyph;
|
||||
|
||||
/// <summary>
|
||||
/// Information for Glyph Icon (Prioritized than IcoPath/Icon if user enable Glyph Icons)
|
||||
/// </summary>
|
||||
public GlyphInfo Glyph { get; init; }
|
||||
public GlyphInfo Glyph
|
||||
{
|
||||
get => _glyph;
|
||||
init => _glyph = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the Glyph Icon after initialization
|
||||
/// </summary>
|
||||
/// <param name="glyph"></param>
|
||||
public void SetGlyph(GlyphInfo glyph)
|
||||
{
|
||||
_glyph = glyph;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An action to take in the form of a function call when the result has been selected.
|
||||
|
|
@ -151,6 +192,7 @@ namespace Flow.Launcher.Plugin
|
|||
/// Its result determines what happens to Flow Launcher's query form:
|
||||
/// when true, the form will be hidden; when false, it will stay in focus.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Func<ActionContext, bool> Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -161,6 +203,7 @@ namespace Flow.Launcher.Plugin
|
|||
/// Its result determines what happens to Flow Launcher's query form:
|
||||
/// when true, the form will be hidden; when false, it will stay in focus.
|
||||
/// </remarks>
|
||||
[JsonIgnore]
|
||||
public Func<ActionContext, ValueTask<bool>> AsyncAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -203,11 +246,13 @@ namespace Flow.Launcher.Plugin
|
|||
/// <example>
|
||||
/// As external information for ContextMenu
|
||||
/// </example>
|
||||
[JsonIgnore]
|
||||
public object ContextData { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Plugin ID that generated this result
|
||||
/// </summary>
|
||||
[JsonInclude]
|
||||
public string PluginID { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -223,6 +268,7 @@ namespace Flow.Launcher.Plugin
|
|||
/// <summary>
|
||||
/// Customized Preview Panel
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public Lazy<UserControl> PreviewPanel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -352,6 +398,7 @@ namespace Flow.Launcher.Plugin
|
|||
/// <summary>
|
||||
/// Delegate to get the preview panel's image
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public IconDelegate PreviewDelegate { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -259,6 +259,9 @@ namespace Flow.Launcher
|
|||
|
||||
await PluginManager.InitializePluginsAsync(_mainVM);
|
||||
|
||||
// Refresh the history results after plugins are initialized so that we can parse the absolute icon paths
|
||||
_mainVM.RefreshLastOpenedHistoryResults();
|
||||
|
||||
// Refresh home page after plugins are initialized because users may open main window during plugin initialization
|
||||
// And home page is created without full plugin list
|
||||
if (_settings.ShowHomePage && _mainVM.QueryResultsSelected() && string.IsNullOrEmpty(_mainVM.QueryText))
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@
|
|||
</Target>
|
||||
|
||||
<Target Name="RemoveDuplicateAnalyzers" BeforeTargets="CoreCompile">
|
||||
<!-- Work around https://github.com/dotnet/wpf/issues/6792 -->
|
||||
<!-- Workaround https://github.com/dotnet/wpf/issues/6792 -->
|
||||
<ItemGroup>
|
||||
<FilteredAnalyzer Include="@(Analyzer->Distinct())" />
|
||||
<Analyzer Remove="@(Analyzer)" />
|
||||
|
|
|
|||
45
Flow.Launcher/Helper/ResultHelper.cs
Normal file
45
Flow.Launcher/Helper/ResultHelper.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Plugin;
|
||||
using Flow.Launcher.Storage;
|
||||
|
||||
namespace Flow.Launcher.Helper;
|
||||
|
||||
#nullable enable
|
||||
|
||||
public static class ResultHelper
|
||||
{
|
||||
public static async Task<Result?> PopulateResultsAsync(LastOpenedHistoryResult item)
|
||||
{
|
||||
return await PopulateResultsAsync(item.PluginID, item.Query, item.Title, item.SubTitle, item.RecordKey);
|
||||
}
|
||||
|
||||
public static async Task<Result?> PopulateResultsAsync(string pluginId, string trimmedQuery, string title, string subTitle, string recordKey)
|
||||
{
|
||||
var plugin = PluginManager.GetPluginForId(pluginId);
|
||||
if (plugin == null) return null;
|
||||
var query = QueryBuilder.Build(trimmedQuery, trimmedQuery, PluginManager.GetNonGlobalPlugins());
|
||||
if (query == null) return null;
|
||||
try
|
||||
{
|
||||
var freshResults = await PluginManager.QueryForPluginAsync(plugin, query, CancellationToken.None);
|
||||
// Try to match by record key first if it is valid, otherwise fall back to title + subtitle match
|
||||
if (string.IsNullOrEmpty(recordKey))
|
||||
{
|
||||
return freshResults?.FirstOrDefault(r => r.Title == title && r.SubTitle == subTitle);
|
||||
}
|
||||
else
|
||||
{
|
||||
return freshResults?.FirstOrDefault(r => r.RecordKey == recordKey) ??
|
||||
freshResults?.FirstOrDefault(r => r.Title == title && r.SubTitle == subTitle);
|
||||
}
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
App.API.LogException(nameof(ResultHelper), $"Failed to query results for {plugin.Metadata.Name}", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -172,6 +172,10 @@
|
|||
<system:String x:Key="homePageToolTip">Show home page results when query text is empty.</system:String>
|
||||
<system:String x:Key="historyResultsForHomePage">Show History Results in Home Page</system:String>
|
||||
<system:String x:Key="historyResultsCountForHomePage">Maximum History Results Shown in Home Page</system:String>
|
||||
<system:String x:Key="historyStyle">History Style</system:String>
|
||||
<system:String x:Key="historyStyleTooltip">Choose the type of history to show in the History and Home Page</system:String>
|
||||
<system:String x:Key="queryHistory">Query history</system:String>
|
||||
<system:String x:Key="executedHistory">Last opened history</system:String>
|
||||
<system:String x:Key="homeToggleBoxToolTip">This can only be edited if plugin supports Home feature and Home Page is enabled.</system:String>
|
||||
<system:String x:Key="showAtTopmost">Show Search Window at Foremost</system:String>
|
||||
<system:String x:Key="showAtTopmostToolTip">Overrides other programs' 'Always on Top' setting and displays Flow in the foremost position.</system:String>
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ namespace Flow.Launcher
|
|||
break;
|
||||
case nameof(Settings.ShowHomePage):
|
||||
case nameof(Settings.ShowHistoryResultsForHomePage):
|
||||
case nameof(Settings.HistoryStyle):
|
||||
if (_viewModel.QueryResultsSelected() && string.IsNullOrEmpty(_viewModel.QueryText))
|
||||
{
|
||||
_viewModel.QueryResults();
|
||||
|
|
@ -859,7 +860,7 @@ namespace Flow.Launcher
|
|||
|
||||
public void UpdatePosition()
|
||||
{
|
||||
// Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910
|
||||
// Initialize call twice to workaround multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910
|
||||
if (_viewModel.IsDialogJumpWindowUnderDialog())
|
||||
{
|
||||
InitializeDialogJumpPosition();
|
||||
|
|
@ -883,7 +884,7 @@ namespace Flow.Launcher
|
|||
|
||||
private void InitializePosition()
|
||||
{
|
||||
// Initialize call twice to work around multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910
|
||||
// Initialize call twice to workaround multi-display alignment issue- https://github.com/Flow-Launcher/Flow.Launcher/issues/2910
|
||||
InitializePositionInner();
|
||||
InitializePositionInner();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -147,6 +147,8 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
|||
public List<LastQueryModeData> LastQueryModes { get; } =
|
||||
DropdownDataGeneric<LastQueryMode>.GetValues<LastQueryModeData>("LastQuery");
|
||||
|
||||
public List<HistoryStyleLocalized> HistoryStyles { get; } = HistoryStyleLocalized.GetValues();
|
||||
|
||||
public bool EnableDialogJump
|
||||
{
|
||||
get => Settings.EnableDialogJump;
|
||||
|
|
@ -213,6 +215,7 @@ public partial class SettingsPaneGeneralViewModel : BaseModel
|
|||
DropdownDataGeneric<SearchWindowAligns>.UpdateLabels(SearchWindowAligns);
|
||||
DropdownDataGeneric<SearchPrecisionScore>.UpdateLabels(SearchPrecisionScores);
|
||||
DropdownDataGeneric<LastQueryMode>.UpdateLabels(LastQueryModes);
|
||||
HistoryStyleLocalized.UpdateLabels(HistoryStyles);
|
||||
DropdownDataGeneric<DoublePinyinSchemas>.UpdateLabels(DoublePinyinSchemas);
|
||||
DropdownDataGeneric<DialogJumpWindowPositions>.UpdateLabels(DialogJumpWindowPositions);
|
||||
DropdownDataGeneric<DialogJumpResultBehaviours>.UpdateLabels(DialogJumpResultBehaviours);
|
||||
|
|
|
|||
|
|
@ -255,6 +255,22 @@
|
|||
SelectedValuePath="Value" />
|
||||
</ui:SettingsCard>
|
||||
|
||||
<ui:SettingsCard
|
||||
Margin="0 14 0 0"
|
||||
Description="{DynamicResource historyStyleTooltip}"
|
||||
Header="{DynamicResource historyStyle}">
|
||||
<ui:SettingsCard.HeaderIcon>
|
||||
<ui:FontIcon Glyph="" />
|
||||
</ui:SettingsCard.HeaderIcon>
|
||||
|
||||
<ComboBox
|
||||
MaxWidth="200"
|
||||
DisplayMemberPath="Display"
|
||||
ItemsSource="{Binding HistoryStyles}"
|
||||
SelectedValue="{Binding Settings.HistoryStyle}"
|
||||
SelectedValuePath="Value" />
|
||||
</ui:SettingsCard>
|
||||
|
||||
<ui:SettingsCard
|
||||
Margin="0 14 0 0"
|
||||
Description="{DynamicResource autoRestartAfterChangingToolTip}"
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@
|
|||
Margin="18 24 0 0"
|
||||
HorizontalAlignment="Left"
|
||||
RenderOptions.BitmapScalingMode="Fant"
|
||||
Source="{Binding IcoPath, IsAsync=True}" />
|
||||
Source="{Binding IcoPathAbsolute, IsAsync=True}" />
|
||||
<Border
|
||||
x:Name="LabelUpdate"
|
||||
Height="12"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
using System;
|
||||
using System;
|
||||
|
||||
namespace Flow.Launcher.Storage
|
||||
{
|
||||
[Obsolete("Use LastOpenedHistoryResult instead. This class will be removed in future versions.")]
|
||||
public class HistoryItem
|
||||
{
|
||||
public string Query { get; set; }
|
||||
|
|
@ -42,4 +43,4 @@ namespace Flow.Launcher.Storage
|
|||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
146
Flow.Launcher/Storage/LastOpenedHistoryResult.cs
Normal file
146
Flow.Launcher/Storage/LastOpenedHistoryResult.cs
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
using System;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Storage;
|
||||
|
||||
/// <summary>
|
||||
/// A serializable result used to record the last opened history for reopening results.
|
||||
/// Inherits common result fields from <see cref="Result"/> and adds the original query and execution time.
|
||||
/// </summary>
|
||||
public class LastOpenedHistoryResult : Result
|
||||
{
|
||||
/// <summary>
|
||||
/// The query string from Query.TrimmedQuery property, it is stored as a string instead of the entire Query class <see cref="Result"/>.
|
||||
/// This is used so results can be reopened or re-run using the serialized query string.
|
||||
/// </summary>
|
||||
public string Query { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The local date and time when this result was executed/opened.
|
||||
/// </summary>
|
||||
public DateTime ExecutedDateTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="LastOpenedHistoryResult"/>.
|
||||
/// </summary>
|
||||
public LastOpenedHistoryResult()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="LastOpenedHistoryResult"/> from an existing <see cref="Result"/>.
|
||||
/// Copies required fields and sets up default reopening actions.
|
||||
/// </summary>
|
||||
/// <param name="result">The original result to create history from.</param>
|
||||
public LastOpenedHistoryResult(Result result)
|
||||
{
|
||||
Title = result.Title;
|
||||
SubTitle = result.SubTitle;
|
||||
PluginID = result.PluginID;
|
||||
Query = result.OriginQuery.TrimmedQuery;
|
||||
OriginQuery = result.OriginQuery;
|
||||
RecordKey = result.RecordKey;
|
||||
IcoPath = result.IcoPath;
|
||||
PluginDirectory = result.PluginDirectory;
|
||||
Glyph = result.Glyph;
|
||||
ExecutedDateTime = DateTime.Now;
|
||||
// Used for Query History style reopening
|
||||
Action = _ =>
|
||||
{
|
||||
App.API.BackToQueryResults();
|
||||
App.API.ChangeQuery(result.OriginQuery.TrimmedQuery);
|
||||
return false;
|
||||
};
|
||||
// Used for Last Opened History style reopening, currently need to be assigned at MainViewModel.cs
|
||||
AsyncAction = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selectively creates a deep copy of the required properties for <see cref="LastOpenedHistoryResult"/>
|
||||
/// based on the style of history- Last Opened or Query.
|
||||
/// This copy should be independent of original and full isolated.
|
||||
/// </summary>
|
||||
/// <returns>A new <see cref="LastOpenedHistoryResult"/> containing the same required data.</returns>
|
||||
public LastOpenedHistoryResult DeepCopyForHistoryStyle(bool isHistoryStyleLastOpened)
|
||||
{
|
||||
// queryValue and glyphValue are captured to ensure they are correctly referenced in the Action delegate.
|
||||
var queryValue = Query;
|
||||
var glyphValue = Glyph;
|
||||
|
||||
var title = string.Empty;
|
||||
var showBadge = false;
|
||||
var badgeIcoPath = string.Empty;
|
||||
var icoPath = string.Empty;
|
||||
var glyph = null as GlyphInfo;
|
||||
|
||||
if (isHistoryStyleLastOpened)
|
||||
{
|
||||
title = Title;
|
||||
icoPath = IcoPath;
|
||||
glyph = glyphValue != null
|
||||
? new GlyphInfo(glyphValue.FontFamily, glyphValue.Glyph)
|
||||
: null;
|
||||
showBadge = true;
|
||||
badgeIcoPath = Constant.HistoryIcon;
|
||||
}
|
||||
else
|
||||
{
|
||||
title = Localize.executeQuery(Query);
|
||||
icoPath = Constant.HistoryIcon;
|
||||
glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C");
|
||||
showBadge = false;
|
||||
}
|
||||
|
||||
return new LastOpenedHistoryResult
|
||||
{
|
||||
Title = title,
|
||||
// Subtitle has datetime which can cause duplicates when saving.
|
||||
SubTitle = Localize.lastExecuteTime(ExecutedDateTime),
|
||||
// Empty PluginID so the source of last opened history results won't be updated, this copy is meant to be temporary.
|
||||
PluginID = string.Empty,
|
||||
Query = Query,
|
||||
OriginQuery = new Query { TrimmedQuery = Query },
|
||||
RecordKey = RecordKey,
|
||||
IcoPath = icoPath,
|
||||
ShowBadge = showBadge,
|
||||
BadgeIcoPath = badgeIcoPath,
|
||||
PluginDirectory = PluginDirectory,
|
||||
// Used for Query History style reopening
|
||||
Action = _ =>
|
||||
{
|
||||
App.API.BackToQueryResults();
|
||||
App.API.ChangeQuery(queryValue);
|
||||
return false;
|
||||
},
|
||||
// Used for Last Opened History style reopening, currently need to be assigned at MainViewModel.cs
|
||||
AsyncAction = null,
|
||||
Glyph = glyph,
|
||||
ExecutedDateTime = ExecutedDateTime
|
||||
// Note: Other properties are left as default — copy if needed.
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified <see cref="Result"/> is equivalent to this history result.
|
||||
/// Comparison uses <see cref="Result.RecordKey"/> when available; otherwise falls back to title/subtitle/plugin id and query.
|
||||
/// </summary>
|
||||
/// <param name="r">The result to compare to.</param>
|
||||
/// <returns><c>true</c> if the results are considered equal; otherwise <c>false</c>.</returns>
|
||||
public bool Equals(Result r)
|
||||
{
|
||||
if (string.IsNullOrEmpty(RecordKey) || string.IsNullOrEmpty(r.RecordKey))
|
||||
{
|
||||
return Title == r.Title
|
||||
&& SubTitle == r.SubTitle
|
||||
&& PluginID == r.PluginID
|
||||
&& Query == r.OriginQuery.TrimmedQuery;
|
||||
}
|
||||
else
|
||||
{
|
||||
return RecordKey == r.RecordKey
|
||||
&& PluginID == r.PluginID
|
||||
&& Query == r.OriginQuery.TrimmedQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,35 +2,118 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
||||
namespace Flow.Launcher.Storage
|
||||
{
|
||||
public class History
|
||||
{
|
||||
[JsonInclude]
|
||||
public List<HistoryItem> Items { get; private set; } = new List<HistoryItem>();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
public List<HistoryItem> Items { get; private set; } = [];
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
private int _maxHistory = 300;
|
||||
[JsonInclude]
|
||||
public List<LastOpenedHistoryResult> LastOpenedHistoryItems { get; private set; } = [];
|
||||
|
||||
public void Add(string query)
|
||||
private readonly int _maxHistory = 300;
|
||||
|
||||
/// <summary>
|
||||
/// Migrate legacy history data (stored in <see cref="Items"/>) into the new
|
||||
/// <see cref="LastOpenedHistoryResult"/> format and append them to
|
||||
/// <see cref="LastOpenedHistoryItems"/>.
|
||||
/// </summary>
|
||||
[Obsolete("For backwards compatibility. Remove after release v2.3.0")]
|
||||
public void PopulateHistoryFromLegacyHistory()
|
||||
{
|
||||
if (string.IsNullOrEmpty(query)) return;
|
||||
if (Items.Count > _maxHistory)
|
||||
if (Items.Count == 0) return;
|
||||
// Migrate old history items to new LastOpenedHistoryItems
|
||||
foreach (var item in Items)
|
||||
{
|
||||
Items.RemoveAt(0);
|
||||
LastOpenedHistoryItems.Add(new LastOpenedHistoryResult
|
||||
{
|
||||
Title = Localize.executeQuery(item.Query),
|
||||
OriginQuery = new Query { TrimmedQuery = item.Query },
|
||||
Query = item.Query,
|
||||
Action = _ =>
|
||||
{
|
||||
App.API.BackToQueryResults();
|
||||
App.API.ChangeQuery(item.Query);
|
||||
return false;
|
||||
},
|
||||
ExecutedDateTime = item.ExecutedDateTime
|
||||
});
|
||||
}
|
||||
Items.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records a result into the last-opened history list (<see cref="LastOpenedHistoryItems"/>).
|
||||
/// This will also update the IcoPath if existing history item has one that is different.
|
||||
/// </summary>
|
||||
/// <param name="result">The result to add to history. Must have a non-empty <see cref="Result.OriginQuery"/>.<see cref="Query.TrimmedQuery"/>.</param>
|
||||
public void Add(Result result)
|
||||
{
|
||||
if (string.IsNullOrEmpty(result.OriginQuery.TrimmedQuery)) return;
|
||||
// History results triggered from homepage do not contain PluginID,
|
||||
// these are intentionally not saved otherwise cause duplicates due to subtitle
|
||||
// containing datetime string.
|
||||
if (string.IsNullOrEmpty(result.PluginID)) return;
|
||||
|
||||
// Maintain the max history limit
|
||||
if (LastOpenedHistoryItems.Count > _maxHistory)
|
||||
{
|
||||
LastOpenedHistoryItems.RemoveAt(0);
|
||||
}
|
||||
|
||||
if (Items.Count > 0 && Items.Last().Query == query)
|
||||
// If the last item is the same as the current result, just update the timestamp and the icon path
|
||||
if (LastOpenedHistoryItems.Count > 0 &&
|
||||
TryGetLastOpenedHistoryResult(result, out var existingHistoryItem))
|
||||
{
|
||||
Items.Last().ExecutedDateTime = DateTime.Now;
|
||||
existingHistoryItem.ExecutedDateTime = DateTime.Now;
|
||||
|
||||
if (existingHistoryItem.IcoPath != result.IcoPath)
|
||||
existingHistoryItem.IcoPath = result.IcoPath;
|
||||
|
||||
if (existingHistoryItem.Glyph?.Glyph != result.Glyph?.Glyph
|
||||
|| existingHistoryItem.Glyph?.FontFamily != result.Glyph?.FontFamily)
|
||||
existingHistoryItem.SetGlyph(result.Glyph);
|
||||
}
|
||||
else
|
||||
{
|
||||
Items.Add(new HistoryItem
|
||||
{
|
||||
Query = query,
|
||||
ExecutedDateTime = DateTime.Now
|
||||
});
|
||||
LastOpenedHistoryItems.Add(new LastOpenedHistoryResult(result));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to find an existing <see cref="LastOpenedHistoryResult"/> in <see cref="LastOpenedHistoryItems"/>
|
||||
/// that is considered equal to the supplied <paramref name="result"/>.
|
||||
/// </summary>
|
||||
private bool TryGetLastOpenedHistoryResult(Result result, out LastOpenedHistoryResult historyItem)
|
||||
{
|
||||
historyItem = LastOpenedHistoryItems.FirstOrDefault(x => x.Equals(result));
|
||||
return historyItem is not null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Flow uses IcoPathAbsolute property to display result the icons. This refreshes the IcoPathAbsolute
|
||||
/// property using current plugin metadata by updating the PluginDirectory property, which in turn also
|
||||
/// updates IcoPath. This keeps the saved icon paths of results updated correctly if flow is moved around.
|
||||
/// </summary>
|
||||
/// <remarks> Call this after plugins are loaded/initialized.</remarks>
|
||||
public void UpdateIcoPathAbsolute()
|
||||
{
|
||||
if (LastOpenedHistoryItems.Count == 0) return;
|
||||
|
||||
foreach (var item in LastOpenedHistoryItems)
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.PluginID)) continue;
|
||||
|
||||
var pluginPair = PluginManager.GetPluginForId(item.PluginID);
|
||||
if (pluginPair == null) continue;
|
||||
|
||||
item.PluginDirectory = pluginPair.Metadata.PluginDirectory;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
|
@ -9,16 +9,17 @@ using System.Threading;
|
|||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using Flow.Launcher.Core.Plugin;
|
||||
using Flow.Launcher.Helper;
|
||||
using Flow.Launcher.Infrastructure;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.DialogJump;
|
||||
using Flow.Launcher.Infrastructure.Hotkey;
|
||||
using Flow.Launcher.Infrastructure.Storage;
|
||||
using Flow.Launcher.Infrastructure.UserSettings;
|
||||
using Flow.Launcher.Plugin;
|
||||
|
|
@ -43,10 +44,10 @@ namespace Flow.Launcher.ViewModel
|
|||
private string _ignoredQueryText; // Used to ignore query text change when switching between context menu and query results
|
||||
|
||||
private readonly FlowLauncherJsonStorage<History> _historyItemsStorage;
|
||||
private readonly FlowLauncherJsonStorage<UserSelectedRecord> _userSelectedRecordStorage;
|
||||
private readonly FlowLauncherJsonStorageTopMostRecord _topMostRecord;
|
||||
private readonly History _history;
|
||||
private int lastHistoryIndex = 1;
|
||||
private readonly FlowLauncherJsonStorage<UserSelectedRecord> _userSelectedRecordStorage;
|
||||
private readonly FlowLauncherJsonStorageTopMostRecord _topMostRecord;
|
||||
private readonly UserSelectedRecord _userSelectedRecord;
|
||||
|
||||
private CancellationTokenSource _updateSource; // Used to cancel old query flows
|
||||
|
|
@ -152,10 +153,10 @@ namespace Flow.Launcher.ViewModel
|
|||
};
|
||||
|
||||
_historyItemsStorage = new FlowLauncherJsonStorage<History>();
|
||||
_userSelectedRecordStorage = new FlowLauncherJsonStorage<UserSelectedRecord>();
|
||||
_topMostRecord = new FlowLauncherJsonStorageTopMostRecord();
|
||||
_history = _historyItemsStorage.Load();
|
||||
_userSelectedRecordStorage = new FlowLauncherJsonStorage<UserSelectedRecord>();
|
||||
_userSelectedRecord = _userSelectedRecordStorage.Load();
|
||||
_topMostRecord = new FlowLauncherJsonStorageTopMostRecord();
|
||||
|
||||
ContextMenu = new ResultsViewModel(Settings, this)
|
||||
{
|
||||
|
|
@ -354,11 +355,17 @@ namespace Flow.Launcher.ViewModel
|
|||
if (QueryResultsSelected())
|
||||
{
|
||||
SelectedResults = History;
|
||||
History.SelectedIndex = _history.Items.Count - 1;
|
||||
if (History.Results.Count > 0)
|
||||
{
|
||||
SelectedResults.SelectedIndex = 0;
|
||||
SelectedResults.SelectedItem = History.Results[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedResults = Results;
|
||||
PreviewSelectedItem = Results.SelectedItem;
|
||||
_ = UpdatePreviewAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -382,10 +389,11 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
public void ReverseHistory()
|
||||
{
|
||||
if (_history.Items.Count > 0)
|
||||
var historyItems = _history.LastOpenedHistoryItems;
|
||||
if (historyItems.Count > 0)
|
||||
{
|
||||
ChangeQueryText(_history.Items[^lastHistoryIndex].Query);
|
||||
if (lastHistoryIndex < _history.Items.Count)
|
||||
ChangeQueryText(historyItems[^lastHistoryIndex].Query);
|
||||
if (lastHistoryIndex < historyItems.Count)
|
||||
{
|
||||
lastHistoryIndex++;
|
||||
}
|
||||
|
|
@ -395,9 +403,10 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
public void ForwardHistory()
|
||||
{
|
||||
if (_history.Items.Count > 0)
|
||||
var historyItems = _history.LastOpenedHistoryItems;
|
||||
if (historyItems.Count > 0)
|
||||
{
|
||||
ChangeQueryText(_history.Items[^lastHistoryIndex].Query);
|
||||
ChangeQueryText(historyItems[^lastHistoryIndex].Query);
|
||||
if (lastHistoryIndex > 1)
|
||||
{
|
||||
lastHistoryIndex--;
|
||||
|
|
@ -428,7 +437,8 @@ namespace Flow.Launcher.ViewModel
|
|||
{
|
||||
// When switch to ContextMenu from QueryResults, but no item being chosen, should do nothing
|
||||
// i.e. Shift+Enter/Ctrl+O right after Alt + Space should do nothing
|
||||
if (SelectedResults.SelectedItem != null)
|
||||
if (SelectedResults.SelectedItem?.Result != null &&
|
||||
!string.IsNullOrEmpty(SelectedResults.SelectedItem.Result.PluginID)) // Do not show context menu for history results
|
||||
{
|
||||
SelectedResults = ContextMenu;
|
||||
}
|
||||
|
|
@ -436,6 +446,8 @@ namespace Flow.Launcher.ViewModel
|
|||
else
|
||||
{
|
||||
SelectedResults = Results;
|
||||
PreviewSelectedItem = Results.SelectedItem;
|
||||
_ = UpdatePreviewAsync();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -489,6 +501,8 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
private async Task OpenResultAsync(string index)
|
||||
{
|
||||
// Must check query results selected before executing the action
|
||||
var queryResultsSelected = QueryResultsSelected();
|
||||
var results = SelectedResults;
|
||||
if (index is not null)
|
||||
{
|
||||
|
|
@ -529,10 +543,12 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
if (QueryResultsSelected())
|
||||
// Record user selected result for result ranking
|
||||
_userSelectedRecord.Add(result);
|
||||
// Add item to history only if it is from results but not context menu or history
|
||||
if (queryResultsSelected)
|
||||
{
|
||||
_userSelectedRecord.Add(result);
|
||||
_history.Add(result.OriginQuery.RawQuery);
|
||||
_history.Add(result);
|
||||
lastHistoryIndex = 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -561,7 +577,7 @@ namespace Flow.Launcher.ViewModel
|
|||
resultsCopy.Add(resultCopy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return resultsCopy;
|
||||
}
|
||||
|
||||
|
|
@ -608,10 +624,11 @@ namespace Flow.Launcher.ViewModel
|
|||
[RelayCommand]
|
||||
private void SelectPrevItem()
|
||||
{
|
||||
var historyItems = _history.LastOpenedHistoryItems;
|
||||
if (QueryResultsSelected() // Results selected
|
||||
&& string.IsNullOrEmpty(QueryText) // No input
|
||||
&& Results.Visibility != Visibility.Visible // No items in result list, e.g. when home page is off and no query text is entered, therefore the view is collapsed.
|
||||
&& _history.Items.Count > 0) // Have history items
|
||||
&& historyItems.Count > 0) // Have history items
|
||||
{
|
||||
lastHistoryIndex = 1;
|
||||
ReverseHistory();
|
||||
|
|
@ -634,6 +651,8 @@ namespace Flow.Launcher.ViewModel
|
|||
if (!QueryResultsSelected())
|
||||
{
|
||||
SelectedResults = Results;
|
||||
PreviewSelectedItem = Results.SelectedItem;
|
||||
_ = UpdatePreviewAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -910,7 +929,7 @@ namespace Flow.Launcher.ViewModel
|
|||
private string _placeholderText;
|
||||
public string PlaceholderText
|
||||
{
|
||||
get => string.IsNullOrEmpty(_placeholderText) ? Localize.queryTextBoxPlaceholder(): _placeholderText;
|
||||
get => string.IsNullOrEmpty(_placeholderText) ? Localize.queryTextBoxPlaceholder() : _placeholderText;
|
||||
set
|
||||
{
|
||||
_placeholderText = value;
|
||||
|
|
@ -1152,6 +1171,7 @@ namespace Flow.Launcher.ViewModel
|
|||
HideInternalPreview();
|
||||
_ = OpenExternalPreviewAsync(path);
|
||||
}
|
||||
|
||||
break;
|
||||
case true
|
||||
when !CanExternalPreviewSelectedResult(out var _):
|
||||
|
|
@ -1243,40 +1263,30 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
var selected = Results.SelectedItem?.Result;
|
||||
|
||||
if (selected != null) // SelectedItem returns null if selection is empty.
|
||||
if (selected != null && // SelectedItem returns null if selection is empty.
|
||||
!string.IsNullOrEmpty(selected.PluginID)) // SelectedItem must have a valid PluginID, history results do not.
|
||||
{
|
||||
List<Result> results;
|
||||
if (selected.PluginID == null) // SelectedItem from history in home page.
|
||||
{
|
||||
results = new()
|
||||
{
|
||||
ContextMenuTopMost(selected)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
results = PluginManager.GetContextMenusForPlugin(selected);
|
||||
results.Add(ContextMenuTopMost(selected));
|
||||
results.Add(ContextMenuPluginInfo(selected));
|
||||
}
|
||||
List<Result> results = PluginManager.GetContextMenusForPlugin(selected);
|
||||
results.Add(ContextMenuTopMost(selected));
|
||||
results.Add(ContextMenuPluginInfo(selected));
|
||||
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
{
|
||||
var filtered = results.Select(x => x.Clone()).Where
|
||||
(
|
||||
r =>
|
||||
{
|
||||
var match = App.API.FuzzySearch(query, r.Title);
|
||||
if (!match.IsSearchPrecisionScoreMet())
|
||||
{
|
||||
match = App.API.FuzzySearch(query, r.SubTitle);
|
||||
}
|
||||
{
|
||||
var match = App.API.FuzzySearch(query, r.Title);
|
||||
if (!match.IsSearchPrecisionScoreMet())
|
||||
{
|
||||
match = App.API.FuzzySearch(query, r.SubTitle);
|
||||
}
|
||||
|
||||
if (!match.IsSearchPrecisionScoreMet()) return false;
|
||||
if (!match.IsSearchPrecisionScoreMet()) return false;
|
||||
|
||||
r.Score = match.Score;
|
||||
return true;
|
||||
}).ToList();
|
||||
r.Score = match.Score;
|
||||
return true;
|
||||
}).ToList();
|
||||
ContextMenu.AddResults(filtered, id);
|
||||
}
|
||||
else
|
||||
|
|
@ -1292,7 +1302,7 @@ namespace Flow.Launcher.ViewModel
|
|||
var query = QueryText.ToLower().Trim();
|
||||
History.Clear();
|
||||
|
||||
var results = GetHistoryItems(_history.Items);
|
||||
var results = GetHistoryItems(_history.LastOpenedHistoryItems);
|
||||
|
||||
if (!string.IsNullOrEmpty(query))
|
||||
{
|
||||
|
|
@ -1309,30 +1319,77 @@ namespace Flow.Launcher.ViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private static List<Result> GetHistoryItems(IEnumerable<HistoryItem> historyItems)
|
||||
private List<Result> GetHistoryItems(IEnumerable<LastOpenedHistoryResult> historyItems, int? maxResult = null)
|
||||
{
|
||||
var results = new List<Result>();
|
||||
foreach (var h in historyItems)
|
||||
|
||||
// Order by executed time descending: Latest -> Oldest
|
||||
historyItems = historyItems.OrderByDescending(x => x.ExecutedDateTime);
|
||||
|
||||
if (Settings.HistoryStyle == HistoryStyle.LastOpened)
|
||||
{
|
||||
var result = new Result
|
||||
{
|
||||
Title = Localize.executeQuery(h.Query),
|
||||
SubTitle = Localize.lastExecuteTime(h.ExecutedDateTime),
|
||||
IcoPath = Constant.HistoryIcon,
|
||||
OriginQuery = new Query { RawQuery = h.Query },
|
||||
Action = _ =>
|
||||
{
|
||||
App.API.BackToQueryResults();
|
||||
App.API.ChangeQuery(h.Query);
|
||||
return false;
|
||||
},
|
||||
Glyph = new GlyphInfo(FontFamily: "/Resources/#Segoe Fluent Icons", Glyph: "\uE81C")
|
||||
};
|
||||
results.Add(result);
|
||||
// Items saved to disk are differentiated by Query also, but LastOpened style only cares about unique results
|
||||
historyItems = historyItems
|
||||
.GroupBy(r => new { r.Title, r.SubTitle, r.PluginID, r.RecordKey })
|
||||
.Select(g => g.First());
|
||||
}
|
||||
|
||||
// Max history results to return for display
|
||||
if (maxResult.HasValue)
|
||||
{
|
||||
historyItems = historyItems.Take(maxResult.Value);
|
||||
}
|
||||
|
||||
foreach (var item in historyItems)
|
||||
{
|
||||
var copiedItem = item.DeepCopyForHistoryStyle(Settings.HistoryStyle == HistoryStyle.LastOpened);
|
||||
|
||||
if (Settings.HistoryStyle == HistoryStyle.LastOpened)
|
||||
{
|
||||
copiedItem.AsyncAction = async c =>
|
||||
{
|
||||
// Use original history item to reflect correct result because properties like subtitle have been modified in copiedItem
|
||||
var reflectResult = await ResultHelper.PopulateResultsAsync(item);
|
||||
if (reflectResult != null)
|
||||
{
|
||||
// Since some actions may need to hide the Flow window to execute
|
||||
// So let us populate the results of them
|
||||
return await reflectResult.ExecuteAsync(c);
|
||||
}
|
||||
|
||||
// If we cannot get the result, fallback to re-query
|
||||
App.API.BackToQueryResults();
|
||||
App.API.ChangeQuery(copiedItem.Query);
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
results.Add(copiedItem);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the last-opened history storage by migrating legacy entries and
|
||||
/// updating stored icon paths to their resolved (absolute) locations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Calls <see cref="History.UpdateIcoPathAbsolute"/> to refresh absolute icon
|
||||
/// paths on the migrated/saved history entries by updating each item's
|
||||
/// <c>PluginDirectory</c> (which in turn resolves <c>IcoPathAbsolute</c>).
|
||||
///
|
||||
/// Important:
|
||||
/// - Plugins must be initialized (their metadata and <c>PluginDirectory</c> set)
|
||||
/// before calling this method; otherwise icon resolution cannot be performed.
|
||||
/// </remarks>
|
||||
internal void RefreshLastOpenedHistoryResults()
|
||||
{
|
||||
_history.PopulateHistoryFromLegacyHistory();
|
||||
|
||||
_history.UpdateIcoPathAbsolute();
|
||||
}
|
||||
|
||||
private async Task QueryResultsAsync(bool searchDelay, bool isReQuery = false, bool reSelect = true)
|
||||
{
|
||||
_updateSource?.Cancel();
|
||||
|
|
@ -1574,10 +1631,8 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
void QueryHistoryTask(CancellationToken token)
|
||||
{
|
||||
// Select last history results and revert its order to make sure last history results are on top
|
||||
var historyItems = _history.Items.TakeLast(Settings.MaxHistoryResultsToShowForHomePage).Reverse();
|
||||
|
||||
var results = GetHistoryItems(historyItems);
|
||||
// Select last history results
|
||||
var results = GetHistoryItems(_history.LastOpenedHistoryItems, Settings.MaxHistoryResultsToShowForHomePage);
|
||||
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private bool GlyphAvailable => Glyph is not null;
|
||||
|
||||
private bool ImgIconAvailable => !string.IsNullOrEmpty(Result.IcoPath) || Result.Icon is not null;
|
||||
private bool ImgIconAvailable => !string.IsNullOrEmpty(Result.IcoPathAbsolute) || Result.Icon is not null;
|
||||
|
||||
private bool BadgeIconAvailable => !string.IsNullOrEmpty(Result.BadgeIcoPath) || Result.BadgeIcon is not null;
|
||||
|
||||
|
|
@ -236,7 +236,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private async Task LoadImageAsync()
|
||||
{
|
||||
var imagePath = Result.IcoPath;
|
||||
var imagePath = Result.IcoPathAbsolute;
|
||||
var iconDelegate = Result.Icon;
|
||||
if (ImageLoader.TryGetValue(imagePath, false, out var img))
|
||||
{
|
||||
|
|
@ -266,7 +266,7 @@ namespace Flow.Launcher.ViewModel
|
|||
|
||||
private async Task LoadPreviewImageAsync()
|
||||
{
|
||||
var imagePath = Result.Preview.PreviewImagePath ?? Result.IcoPath;
|
||||
var imagePath = Result.Preview.PreviewImagePath ?? Result.IcoPathAbsolute;
|
||||
var iconDelegate = Result.Preview.PreviewDelegate ?? Result.Icon;
|
||||
if (ImageLoader.TryGetValue(imagePath, true, out var img))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@
|
|||
$(OutputPath)runtimes\linux-s390x;
|
||||
$(OutputPath)runtimes\linux-x64;
|
||||
$(OutputPath)runtimes\linux-x86;
|
||||
$(OutputPath)runtimes\linux-musl-riscv64;
|
||||
$(OutputPath)runtimes\linux-riscv64;
|
||||
$(OutputPath)runtimes\maccatalyst-arm64;
|
||||
$(OutputPath)runtimes\maccatalyst-x64;
|
||||
$(OutputPath)runtimes\osx;
|
||||
|
|
@ -74,6 +76,8 @@
|
|||
$(PublishDir)runtimes\linux-s390x;
|
||||
$(PublishDir)runtimes\linux-x64;
|
||||
$(PublishDir)runtimes\linux-x86;
|
||||
$(PublishDir)runtimes\linux-musl-riscv64;
|
||||
$(PublishDir)runtimes\linux-riscv64;
|
||||
$(PublishDir)runtimes\maccatalyst-arm64;
|
||||
$(PublishDir)runtimes\maccatalyst-x64;
|
||||
$(PublishDir)runtimes\osx;
|
||||
|
|
@ -110,4 +114,4 @@
|
|||
<PackageReference Include="SkiaSharp" Version="3.119.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
Loading…
Reference in a new issue