Frequency navigation

This commit is contained in:
2023-02-27 23:19:04 +01:00
parent 650b650cd8
commit 05c1f026e6
10 changed files with 130 additions and 42 deletions

View File

@@ -11,4 +11,8 @@
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" /> <ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-preview5" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,3 +1,4 @@
using Avalonia.Input;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
namespace FileTime.App.FrequencyNavigation.ViewModels; namespace FileTime.App.FrequencyNavigation.ViewModels;
@@ -9,4 +10,5 @@ public interface IFrequencyNavigationViewModel : IModalViewModel
string SearchText { get; set; } string SearchText { get; set; }
string SelectedItem { get; set; } string SelectedItem { get; set; }
void Close(); void Close();
void HandleKeyDown(KeyEventArgs keyEventArgs);
} }

View File

@@ -89,7 +89,7 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I
if (TryAgeContainerScores() || DateTime.Now - _lastSave > TimeSpan.FromMinutes(5)) if (TryAgeContainerScores() || DateTime.Now - _lastSave > TimeSpan.FromMinutes(5))
{ {
} }
//TODO: move to if above
await SaveStateAsync(); await SaveStateAsync();
} }
catch (Exception e) catch (Exception e)
@@ -113,7 +113,7 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I
var itemsToRemove = new List<string>(); var itemsToRemove = new List<string>();
foreach (var container in _containerScores) foreach (var container in _containerScores)
{ {
var newScore = (int) Math.Floor(container.Value.Score * 0.9); var newScore = (int)Math.Floor(container.Value.Score * 0.9);
if (newScore > 0) if (newScore > 0)
{ {
container.Value.Score = newScore; container.Value.Score = newScore;
@@ -136,14 +136,18 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I
return new List<string>(); return new List<string>();
_saveLock.Wait(); _saveLock.Wait();
var matchingContainers = _containerScores try
{
return _containerScores
.Where(c => c.Key.Contains(searchText, StringComparison.OrdinalIgnoreCase)) .Where(c => c.Key.Contains(searchText, StringComparison.OrdinalIgnoreCase))
.OrderBy(c => GetWeightedScore(c.Value.Score, c.Value.LastAccessed)) .OrderBy(c => GetWeightedScore(c.Value.Score, c.Value.LastAccessed))
.Select(c => c.Key) .Select(c => c.Key)
.ToList(); .ToList();
}
finally
{
_saveLock.Release(); _saveLock.Release();
return matchingContainers; }
} }
private int GetWeightedScore(int score, DateTime lastAccess) private int GetWeightedScore(int score, DateTime lastAccess)
@@ -159,36 +163,48 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I
}; };
} }
public async Task InitAsync() public async Task InitAsync() => await LoadStateAsync();
{
await LoadStateAsync();
}
private async Task LoadStateAsync() private async Task LoadStateAsync()
{ {
if (!File.Exists(_dbPath)) if (!File.Exists(_dbPath))
return; return;
try
{
await _saveLock.WaitAsync(); await _saveLock.WaitAsync();
_logger.LogTrace("Loading frequency navigation state from file '{DbPath}'", _dbPath);
await using var dbStream = File.OpenRead(_dbPath); await using var dbStream = File.OpenRead(_dbPath);
var containerScores = await JsonSerializer.DeserializeAsync<Dictionary<string, ContainerFrequencyData>>(dbStream); var containerScores = await JsonSerializer.DeserializeAsync<Dictionary<string, ContainerFrequencyData>>(dbStream);
if (containerScores is null) return; if (containerScores is null) return;
_containerScores = containerScores; _containerScores = containerScores;
}
catch (Exception e)
{
_logger.LogError(e, "Error loading frequency navigation state");
}
finally
{
_saveLock.Release(); _saveLock.Release();
} }
public async Task ExitAsync()
{
await SaveStateAsync();
} }
public async Task ExitAsync() => await SaveStateAsync();
private async Task SaveStateAsync() private async Task SaveStateAsync()
{ {
await _saveLock.WaitAsync(); await _saveLock.WaitAsync();
try
{
_lastSave = DateTime.Now; _lastSave = DateTime.Now;
await using var dbStream = File.OpenWrite(_dbPath); await using var dbStream = File.Create(_dbPath);
await JsonSerializer.SerializeAsync(dbStream, _containerScores); await JsonSerializer.SerializeAsync(dbStream, _containerScores);
dbStream.Flush();
}
finally
{
_saveLock.Release(); _saveLock.Release();
} }
}
} }

View File

@@ -1,11 +1,18 @@
using Avalonia.Input;
using FileTime.App.Core.Services;
using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.App.FrequencyNavigation.Services; using FileTime.App.FrequencyNavigation.Services;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
using MvvmGen; using MvvmGen;
namespace FileTime.App.FrequencyNavigation.ViewModels; namespace FileTime.App.FrequencyNavigation.ViewModels;
[ViewModel] [ViewModel]
[Inject(typeof(IFrequencyNavigationService), "_frequencyNavigationService")] [Inject(typeof(IFrequencyNavigationService), "_frequencyNavigationService")]
[Inject(typeof(IUserCommandHandlerService), "_userCommandHandlerService")]
[Inject(typeof(ITimelessContentProvider), "_timelessContentProvider")]
public partial class FrequencyNavigationViewModel : IFrequencyNavigationViewModel public partial class FrequencyNavigationViewModel : IFrequencyNavigationViewModel
{ {
private string _searchText; private string _searchText;
@@ -29,18 +36,48 @@ public partial class FrequencyNavigationViewModel : IFrequencyNavigationViewMode
} }
public void Close() public void Close()
=> _frequencyNavigationService.CloseNavigationWindow();
public async void HandleKeyDown(KeyEventArgs keyEventArgs)
{ {
_frequencyNavigationService.CloseNavigationWindow(); if (keyEventArgs.Key == Key.Down)
{
var nextItem = FilteredMatches.SkipWhile(i => i != SelectedItem).Skip(1).FirstOrDefault();
if (nextItem is not null)
{
SelectedItem = nextItem;
}
}
else if (keyEventArgs.Key == Key.Up)
{
var previousItem = FilteredMatches.TakeWhile(i => i != SelectedItem).LastOrDefault();
if (previousItem is not null)
{
SelectedItem = previousItem;
}
}
else if (keyEventArgs.Key == Key.Enter)
{
var targetContainer = await _timelessContentProvider.GetItemByFullNameAsync(new FullName(SelectedItem), PointInTime.Present);
var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, targetContainer));
await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
Close();
}
} }
partial void OnInitialize() partial void OnInitialize()
{ => _showWindow = _frequencyNavigationService.ShowWindow;
_showWindow = _frequencyNavigationService.ShowWindow;
}
private void UpdateFilteredMatches() private void UpdateFilteredMatches()
{ {
FilteredMatches = new List<string>(_frequencyNavigationService.GetMatchingContainers(_searchText)); FilteredMatches = new List<string>(_frequencyNavigationService.GetMatchingContainers(_searchText));
if (FilteredMatches.Contains(SelectedItem)) return;
SelectedItem = FilteredMatches.Count > 0
? FilteredMatches[0]
: null;
} }
string IModalViewModel.Name => "FrequencyNavigation"; string IModalViewModel.Name => "FrequencyNavigation";

View File

@@ -1,7 +1,7 @@
{ {
"Serilog": { "Serilog": {
"MinimumLevel": { "MinimumLevel": {
"Default": "Debug", "Default": "Verbose",
"Override": { "Override": {
"Microsoft": "Information", "Microsoft": "Information",
"System": "Warning" "System": "Warning"

View File

@@ -18,6 +18,8 @@ public class ToastMessageSink : ILogEventSink
if (logEvent.Level >= LogEventLevel.Error) if (logEvent.Level >= LogEventLevel.Error)
{ {
var message = logEvent.RenderMessage(); var message = logEvent.RenderMessage();
if (logEvent.Exception is not null)
message += $" {logEvent.Exception.Message}";
dialogService.ShowToastMessage(message); dialogService.ShowToastMessage(message);
} }
} }

View File

@@ -40,6 +40,21 @@
</Setter> </Setter>
</Style> </Style>
<Style Selector="ListBox.CommandPalette">
<Setter Property="Background" Value="Transparent" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
<Style Selector="ListBox.CommandPalette &gt; ListBoxItem">
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
</Style>
<Style Selector="ListBox.CommandPalette &gt; ListBoxItem[IsSelected=true] TextBlock">
<Setter Property="Foreground" Value="{DynamicResource SelectedItemForegroundBrush}" />
</Style>
<Style Selector="Grid.SidebarContainerPresenter"> <Style Selector="Grid.SidebarContainerPresenter">
<Setter Property="Background" Value="#01000000" /> <Setter Property="Background" Value="#01000000" />
</Style> </Style>

View File

@@ -9,21 +9,28 @@
d:DesignWidth="800" d:DesignWidth="800"
x:CompileBindings="True" x:CompileBindings="True"
x:DataType="viewModels:IFrequencyNavigationViewModel" x:DataType="viewModels:IFrequencyNavigationViewModel"
Background="{DynamicResource ContainerBackgroundColor}"
mc:Ignorable="d"> mc:Ignorable="d">
<UserControl.Styles>
<StyleInclude Source="avares://FileTime.GuiApp/Resources/Styles.axaml" />
</UserControl.Styles>
<Grid RowDefinitions="Auto,*"> <Grid RowDefinitions="Auto,*">
<TextBox <TextBox
Focusable="True"
KeyDown="Search_OnKeyDown" KeyDown="Search_OnKeyDown"
Text="{Binding SearchText, Mode=TwoWay}" /> Text="{Binding SearchText, Mode=TwoWay}" />
<ItemsRepeater <ListBox
Grid.Row="1" Grid.Row="1"
Items="{Binding FilteredMatches}"> Classes="CommandPalette"
<ItemsRepeater.ItemTemplate> Items="{Binding FilteredMatches}"
SelectedItem="{Binding SelectedItem}">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="x:String"> <DataTemplate x:DataType="x:String">
<Grid Margin="5"> <Grid Margin="5">
<TextBlock Text="{Binding}" /> <TextBlock Text="{Binding}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ItemsRepeater.ItemTemplate> </ListBox.ItemTemplate>
</ItemsRepeater> </ListBox>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -26,5 +26,9 @@ public partial class FrequencyNavigation : UserControl
{ {
viewModel.Close(); viewModel.Close();
} }
else
{
viewModel.HandleKeyDown(e);
}
} }
} }

View File

@@ -719,12 +719,13 @@
<Border <Border
DataContext="{Binding FrequencyNavigationService.CurrentModal}" DataContext="{Binding FrequencyNavigationService.CurrentModal}"
Margin="100"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Background="{DynamicResource BarelyTransparentBackgroundColor}" Background="{DynamicResource BarelyTransparentBackgroundColor}"
IsVisible="{Binding ShowWindow^, FallbackValue=False}"> IsVisible="{Binding ShowWindow^, FallbackValue=False}">
<Grid Margin="100">
<local:FrequencyNavigation /> <local:FrequencyNavigation />
</Grid>
</Border> </Border>
</Grid> </Grid>