Frequency navigation WIP
This commit is contained in:
@@ -9,4 +9,5 @@ public interface IModalService
|
|||||||
|
|
||||||
void OpenModal(IModalViewModel modalToOpen);
|
void OpenModal(IModalViewModel modalToOpen);
|
||||||
void CloseModal(IModalViewModel modalToClose);
|
void CloseModal(IModalViewModel modalToClose);
|
||||||
|
T OpenModal<T>() where T : IModalViewModel;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
namespace FileTime.App.Core.UserCommand;
|
||||||
|
|
||||||
|
public class GoByFrequencyCommand : IIdentifiableUserCommand
|
||||||
|
{
|
||||||
|
public const string CommandName = "go_by_frequency";
|
||||||
|
|
||||||
|
public static GoByFrequencyCommand Instance { get; } = new();
|
||||||
|
|
||||||
|
private GoByFrequencyCommand()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UserCommandID => CommandName;
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
<ProjectReference Include="..\..\Tools\FileTime.Tools\FileTime.Tools.csproj" />
|
<ProjectReference Include="..\..\Tools\FileTime.Tools\FileTime.Tools.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||||
<ProjectReference Include="..\..\Core\FileTime.Core.Command\FileTime.Core.Command.csproj" />
|
<ProjectReference Include="..\..\Core\FileTime.Core.Command\FileTime.Core.Command.csproj" />
|
||||||
|
<ProjectReference Include="..\FileTime.App.FrequencyNavigation.Abstractions\FileTime.App.FrequencyNavigation.Abstractions.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public class TabPersistenceService : ITabPersistenceService
|
|||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
_localContentProvider = localContentProvider;
|
_localContentProvider = localContentProvider;
|
||||||
|
|
||||||
_jsonOptions = new JsonSerializerOptions()
|
_jsonOptions = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = true,
|
PropertyNameCaseInsensitive = true,
|
||||||
WriteIndented = true
|
WriteIndented = true
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using FileTime.App.Core.Extensions;
|
|||||||
using FileTime.App.Core.Models.Enums;
|
using FileTime.App.Core.Models.Enums;
|
||||||
using FileTime.App.Core.UserCommand;
|
using FileTime.App.Core.UserCommand;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.App.FrequencyNavigation.Services;
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Services;
|
using FileTime.Core.Services;
|
||||||
@@ -20,6 +21,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
private readonly IUserCommandHandlerService _userCommandHandlerService;
|
||||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||||
private readonly IUserCommunicationService _userCommunicationService;
|
private readonly IUserCommunicationService _userCommunicationService;
|
||||||
|
private readonly IFrequencyNavigationService _frequencyNavigationService;
|
||||||
private ITabViewModel? _selectedTab;
|
private ITabViewModel? _selectedTab;
|
||||||
private IContainer? _currentLocation;
|
private IContainer? _currentLocation;
|
||||||
private IItemViewModel? _currentSelectedItem;
|
private IItemViewModel? _currentSelectedItem;
|
||||||
@@ -32,7 +34,8 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
ILocalContentProvider localContentProvider,
|
ILocalContentProvider localContentProvider,
|
||||||
IUserCommandHandlerService userCommandHandlerService,
|
IUserCommandHandlerService userCommandHandlerService,
|
||||||
ITimelessContentProvider timelessContentProvider,
|
ITimelessContentProvider timelessContentProvider,
|
||||||
IUserCommunicationService userCommunicationService) : base(appState)
|
IUserCommunicationService userCommunicationService,
|
||||||
|
IFrequencyNavigationService frequencyNavigationService) : base(appState)
|
||||||
{
|
{
|
||||||
_appState = appState;
|
_appState = appState;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
@@ -40,6 +43,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
_userCommandHandlerService = userCommandHandlerService;
|
_userCommandHandlerService = userCommandHandlerService;
|
||||||
_timelessContentProvider = timelessContentProvider;
|
_timelessContentProvider = timelessContentProvider;
|
||||||
_userCommunicationService = userCommunicationService;
|
_userCommunicationService = userCommunicationService;
|
||||||
|
_frequencyNavigationService = frequencyNavigationService;
|
||||||
|
|
||||||
SaveSelectedTab(t => _selectedTab = t);
|
SaveSelectedTab(t => _selectedTab = t);
|
||||||
SaveCurrentSelectedItem(i => _currentSelectedItem = i);
|
SaveCurrentSelectedItem(i => _currentSelectedItem = i);
|
||||||
@@ -53,6 +57,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
new TypeUserCommandHandler<CloseTabCommand>(CloseTab),
|
new TypeUserCommandHandler<CloseTabCommand>(CloseTab),
|
||||||
new TypeUserCommandHandler<EnterRapidTravelCommand>(EnterRapidTravel),
|
new TypeUserCommandHandler<EnterRapidTravelCommand>(EnterRapidTravel),
|
||||||
new TypeUserCommandHandler<ExitRapidTravelCommand>(ExitRapidTravel),
|
new TypeUserCommandHandler<ExitRapidTravelCommand>(ExitRapidTravel),
|
||||||
|
new TypeUserCommandHandler<GoByFrequencyCommand>(GoByFrequency),
|
||||||
new TypeUserCommandHandler<GoToHomeCommand>(GoToHome),
|
new TypeUserCommandHandler<GoToHomeCommand>(GoToHome),
|
||||||
new TypeUserCommandHandler<GoToPathCommand>(GoToPath),
|
new TypeUserCommandHandler<GoToPathCommand>(GoToPath),
|
||||||
new TypeUserCommandHandler<GoToProviderCommand>(GoToProvider),
|
new TypeUserCommandHandler<GoToProviderCommand>(GoToProvider),
|
||||||
@@ -71,6 +76,12 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Task GoByFrequency()
|
||||||
|
{
|
||||||
|
_frequencyNavigationService.OpenNavigationWindow();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task GoToPath()
|
private async Task GoToPath()
|
||||||
{
|
{
|
||||||
var pathInput = new TextInputElement("Path");
|
var pathInput = new TextInputElement("Path");
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ public class DefaultIdentifiableCommandHandlerRegister : IStartupHandler
|
|||||||
AddUserCommand(DeleteCommand.SoftDelete);
|
AddUserCommand(DeleteCommand.SoftDelete);
|
||||||
AddUserCommand(EnterRapidTravelCommand.Instance);
|
AddUserCommand(EnterRapidTravelCommand.Instance);
|
||||||
AddUserCommand(ExitRapidTravelCommand.Instance);
|
AddUserCommand(ExitRapidTravelCommand.Instance);
|
||||||
|
AddUserCommand(GoByFrequencyCommand.Instance);
|
||||||
AddUserCommand(GoToHomeCommand.Instance);
|
AddUserCommand(GoToHomeCommand.Instance);
|
||||||
AddUserCommand(GoToPathCommand.Instance);
|
AddUserCommand(GoToPathCommand.Instance);
|
||||||
AddUserCommand(GoToProviderCommand.Instance);
|
AddUserCommand(GoToProviderCommand.Instance);
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ public static class DependencyInjection
|
|||||||
serviceCollection.TryAddSingleton<IApplicationSettings, ApplicationSettings>();
|
serviceCollection.TryAddSingleton<IApplicationSettings, ApplicationSettings>();
|
||||||
serviceCollection.TryAddSingleton<ITabPersistenceService, TabPersistenceService>();
|
serviceCollection.TryAddSingleton<ITabPersistenceService, TabPersistenceService>();
|
||||||
serviceCollection.TryAddTransient<ITab, Tab>();
|
serviceCollection.TryAddTransient<ITab, Tab>();
|
||||||
|
serviceCollection.TryAddSingleton<ITabEvents, TabEvents>();
|
||||||
serviceCollection.AddSingleton<IExitHandler, ITabPersistenceService>(sp => sp.GetRequiredService<ITabPersistenceService>());
|
serviceCollection.AddSingleton<IExitHandler, ITabPersistenceService>(sp => sp.GetRequiredService<ITabPersistenceService>());
|
||||||
serviceCollection.AddSingleton<IStartupHandler, ITabPersistenceService>(sp => sp.GetRequiredService<ITabPersistenceService>());
|
serviceCollection.AddSingleton<IStartupHandler, ITabPersistenceService>(sp => sp.GetRequiredService<ITabPersistenceService>());
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>FileTime.App.FrequencyNavigation</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using FileTime.App.FrequencyNavigation.ViewModels;
|
||||||
|
|
||||||
|
namespace FileTime.App.FrequencyNavigation.Services;
|
||||||
|
|
||||||
|
public interface IFrequencyNavigationService
|
||||||
|
{
|
||||||
|
IObservable<bool> ShowWindow { get; }
|
||||||
|
IFrequencyNavigationViewModel? CurrentModal { get; }
|
||||||
|
void OpenNavigationWindow();
|
||||||
|
void CloseNavigationWindow();
|
||||||
|
IList<string> GetMatchingContainers(string searchText);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using FileTime.App.Core.ViewModels;
|
||||||
|
|
||||||
|
namespace FileTime.App.FrequencyNavigation.ViewModels;
|
||||||
|
|
||||||
|
public interface IFrequencyNavigationViewModel : IModalViewModel
|
||||||
|
{
|
||||||
|
IObservable<bool> ShowWindow { get; }
|
||||||
|
List<string> FilteredMatches { get; }
|
||||||
|
string SearchText { get; set; }
|
||||||
|
string SelectedItem { get; set; }
|
||||||
|
void Close();
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Core\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
||||||
|
<ProjectReference Include="..\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||||
|
<ProjectReference Include="..\FileTime.App.FrequencyNavigation.Abstractions\FileTime.App.FrequencyNavigation.Abstractions.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="7.0.0" />
|
||||||
|
<PackageReference Include="MvvmGen" Version="1.1.5" />
|
||||||
|
<PackageReference Include="PropertyChanged.SourceGenerator" Version="1.0.8">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="System.Reactive" Version="5.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace FileTime.App.FrequencyNavigation.Models;
|
||||||
|
|
||||||
|
public class ContainerFrequencyData
|
||||||
|
{
|
||||||
|
public int Score { get; set; } = 1;
|
||||||
|
public DateTime LastAccessed { get; set; } = DateTime.Now;
|
||||||
|
}
|
||||||
@@ -0,0 +1,194 @@
|
|||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Reactive.Subjects;
|
||||||
|
using System.Text.Json;
|
||||||
|
using FileTime.App.Core.Models;
|
||||||
|
using FileTime.App.Core.Services;
|
||||||
|
using FileTime.App.FrequencyNavigation.Models;
|
||||||
|
using FileTime.App.FrequencyNavigation.ViewModels;
|
||||||
|
using FileTime.Core.Models;
|
||||||
|
using FileTime.Core.Services;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PropertyChanged.SourceGenerator;
|
||||||
|
|
||||||
|
namespace FileTime.App.FrequencyNavigation.Services;
|
||||||
|
|
||||||
|
public partial class FrequencyNavigationService : IFrequencyNavigationService, IStartupHandler, IExitHandler
|
||||||
|
{
|
||||||
|
private const int MaxAge = 10_000;
|
||||||
|
|
||||||
|
private DateTime _lastSave = DateTime.Now;
|
||||||
|
private readonly ILogger<FrequencyNavigationService> _logger;
|
||||||
|
private readonly IModalService _modalService;
|
||||||
|
private readonly SemaphoreSlim _saveLock = new(1);
|
||||||
|
private Dictionary<string, ContainerFrequencyData> _containerScores = new();
|
||||||
|
private readonly BehaviorSubject<bool> _showWindow = new(false);
|
||||||
|
private readonly string _dbPath;
|
||||||
|
[Notify] IFrequencyNavigationViewModel? _currentModal;
|
||||||
|
IObservable<bool> IFrequencyNavigationService.ShowWindow => _showWindow.AsObservable();
|
||||||
|
|
||||||
|
public FrequencyNavigationService(
|
||||||
|
ITabEvents tabEvents,
|
||||||
|
IApplicationSettings applicationSettings,
|
||||||
|
ILogger<FrequencyNavigationService> logger,
|
||||||
|
IModalService modalService)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_modalService = modalService;
|
||||||
|
_dbPath = Path.Combine(applicationSettings.AppDataRoot, "frequencyNavigationScores.json");
|
||||||
|
tabEvents.LocationChanged += OnTabLocationChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnTabLocationChanged(object? sender, TabLocationChanged e)
|
||||||
|
{
|
||||||
|
IncreaseContainerScore(e.Location);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenNavigationWindow()
|
||||||
|
{
|
||||||
|
_showWindow.OnNext(true);
|
||||||
|
CurrentModal = _modalService.OpenModal<IFrequencyNavigationViewModel>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseNavigationWindow()
|
||||||
|
{
|
||||||
|
_showWindow.OnNext(false);
|
||||||
|
if (_currentModal is not null)
|
||||||
|
{
|
||||||
|
_modalService.CloseModal(_currentModal);
|
||||||
|
CurrentModal = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void IncreaseContainerScore(FullName containerName)
|
||||||
|
{
|
||||||
|
await _saveLock.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var containerNameString = containerName.Path;
|
||||||
|
if (_containerScores.ContainsKey(containerNameString))
|
||||||
|
{
|
||||||
|
_containerScores[containerNameString].Score++;
|
||||||
|
_containerScores[containerNameString].LastAccessed = DateTime.Now;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_containerScores.Add(containerNameString, new ContainerFrequencyData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Error increasing container score");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_saveLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (TryAgeContainerScores() || DateTime.Now - _lastSave > TimeSpan.FromMinutes(5))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
await SaveStateAsync();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError(e, "Error aging container scores");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryAgeContainerScores()
|
||||||
|
{
|
||||||
|
if (_containerScores.Select(c => c.Value.Score).Sum() < MaxAge)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
AgeContainerScores();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AgeContainerScores()
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var itemsToRemove = new List<string>();
|
||||||
|
foreach (var container in _containerScores)
|
||||||
|
{
|
||||||
|
var newScore = (int) Math.Floor(container.Value.Score * 0.9);
|
||||||
|
if (newScore > 0)
|
||||||
|
{
|
||||||
|
container.Value.Score = newScore;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemsToRemove.Add(container.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var itemToRemove in itemsToRemove)
|
||||||
|
{
|
||||||
|
_containerScores.Remove(itemToRemove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IList<string> GetMatchingContainers(string searchText)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(searchText))
|
||||||
|
return new List<string>();
|
||||||
|
|
||||||
|
_saveLock.Wait();
|
||||||
|
var matchingContainers = _containerScores
|
||||||
|
.Where(c => c.Key.Contains(searchText, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.OrderBy(c => GetWeightedScore(c.Value.Score, c.Value.LastAccessed))
|
||||||
|
.Select(c => c.Key)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
_saveLock.Release();
|
||||||
|
return matchingContainers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetWeightedScore(int score, DateTime lastAccess)
|
||||||
|
{
|
||||||
|
var now = DateTime.Now;
|
||||||
|
var timeSinceLastAccess = now - lastAccess;
|
||||||
|
return timeSinceLastAccess.TotalHours switch
|
||||||
|
{
|
||||||
|
< 1 => score *= 4,
|
||||||
|
< 24 => score *= 2,
|
||||||
|
< 168 => score /= 2,
|
||||||
|
_ => score /= 4
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InitAsync()
|
||||||
|
{
|
||||||
|
await LoadStateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadStateAsync()
|
||||||
|
{
|
||||||
|
if (!File.Exists(_dbPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
await _saveLock.WaitAsync();
|
||||||
|
await using var dbStream = File.OpenRead(_dbPath);
|
||||||
|
var containerScores = await JsonSerializer.DeserializeAsync<Dictionary<string, ContainerFrequencyData>>(dbStream);
|
||||||
|
if (containerScores is null) return;
|
||||||
|
|
||||||
|
_containerScores = containerScores;
|
||||||
|
_saveLock.Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExitAsync()
|
||||||
|
{
|
||||||
|
await SaveStateAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SaveStateAsync()
|
||||||
|
{
|
||||||
|
await _saveLock.WaitAsync();
|
||||||
|
_lastSave = DateTime.Now;
|
||||||
|
await using var dbStream = File.OpenWrite(_dbPath);
|
||||||
|
await JsonSerializer.SerializeAsync(dbStream, _containerScores);
|
||||||
|
_saveLock.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/AppCommon/FileTime.App.FrequencyNavigation/Startup.cs
Normal file
20
src/AppCommon/FileTime.App.FrequencyNavigation/Startup.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using FileTime.App.Core.Services;
|
||||||
|
using FileTime.App.FrequencyNavigation.Services;
|
||||||
|
using FileTime.App.FrequencyNavigation.ViewModels;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
|
|
||||||
|
namespace FileTime.App.FrequencyNavigation;
|
||||||
|
|
||||||
|
public static class Startup
|
||||||
|
{
|
||||||
|
public static IServiceCollection AddFrequencyNavigation(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.TryAddTransient<IFrequencyNavigationViewModel, FrequencyNavigationViewModel>();
|
||||||
|
services.AddSingleton<FrequencyNavigationService>();
|
||||||
|
services.TryAddSingleton<IFrequencyNavigationService>(sp => sp.GetRequiredService<FrequencyNavigationService>());
|
||||||
|
services.AddSingleton<IStartupHandler>(sp => sp.GetRequiredService<FrequencyNavigationService>());
|
||||||
|
services.AddSingleton<IExitHandler>(sp => sp.GetRequiredService<FrequencyNavigationService>());
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using FileTime.App.FrequencyNavigation.Services;
|
||||||
|
using MvvmGen;
|
||||||
|
|
||||||
|
namespace FileTime.App.FrequencyNavigation.ViewModels;
|
||||||
|
|
||||||
|
[ViewModel]
|
||||||
|
[Inject(typeof(IFrequencyNavigationService), "_frequencyNavigationService")]
|
||||||
|
public partial class FrequencyNavigationViewModel : IFrequencyNavigationViewModel
|
||||||
|
{
|
||||||
|
private string _searchText;
|
||||||
|
|
||||||
|
[Property] private IObservable<bool> _showWindow;
|
||||||
|
[Property] private List<string> _filteredMatches;
|
||||||
|
[Property] private string _selectedItem;
|
||||||
|
|
||||||
|
public string SearchText
|
||||||
|
{
|
||||||
|
get => _searchText;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (_searchText == value) return;
|
||||||
|
|
||||||
|
_searchText = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
|
||||||
|
UpdateFilteredMatches();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
_frequencyNavigationService.CloseNavigationWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
partial void OnInitialize()
|
||||||
|
{
|
||||||
|
_showWindow = _frequencyNavigationService.ShowWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateFilteredMatches()
|
||||||
|
{
|
||||||
|
FilteredMatches = new List<string>(_frequencyNavigationService.GetMatchingContainers(_searchText));
|
||||||
|
}
|
||||||
|
|
||||||
|
string IModalViewModel.Name => "FrequencyNavigation";
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
|
using DynamicData;
|
||||||
using FileTime.Core.ContentAccess;
|
using FileTime.Core.ContentAccess;
|
||||||
using FileTime.Core.Enums;
|
using FileTime.Core.Enums;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
@@ -21,7 +22,7 @@ public interface IItem
|
|||||||
string? Attributes { get; }
|
string? Attributes { get; }
|
||||||
AbsolutePathType Type { get; }
|
AbsolutePathType Type { get; }
|
||||||
PointInTime PointInTime { get; }
|
PointInTime PointInTime { get; }
|
||||||
IObservable<IEnumerable<Exception>> Exceptions { get; }
|
IObservable<IChangeSet<Exception>> Exceptions { get; }
|
||||||
ReadOnlyExtensionCollection Extensions { get; }
|
ReadOnlyExtensionCollection Extensions { get; }
|
||||||
|
|
||||||
T? GetExtension<T>() => (T?)Extensions.FirstOrDefault(i => i is T);
|
T? GetExtension<T>() => (T?)Extensions.FirstOrDefault(i => i is T);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using FileTime.Core.Services;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Models;
|
||||||
|
|
||||||
|
public class TabLocationChanged : EventArgs
|
||||||
|
{
|
||||||
|
public FullName Location { get; }
|
||||||
|
public ITab Tab { get; }
|
||||||
|
|
||||||
|
public TabLocationChanged(FullName location, ITab tab)
|
||||||
|
{
|
||||||
|
Location = location;
|
||||||
|
Tab = tab;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Services;
|
||||||
|
|
||||||
|
public interface ITabEvents
|
||||||
|
{
|
||||||
|
event EventHandler<TabLocationChanged> LocationChanged;
|
||||||
|
void OnLocationChanged(ITab tab, FullName location);
|
||||||
|
}
|
||||||
@@ -48,7 +48,8 @@ public abstract class ContentProviderBase : IContentProvider
|
|||||||
public AbsolutePathType Type => AbsolutePathType.Container;
|
public AbsolutePathType Type => AbsolutePathType.Container;
|
||||||
public PointInTime PointInTime { get; } = PointInTime.Eternal;
|
public PointInTime PointInTime { get; } = PointInTime.Eternal;
|
||||||
|
|
||||||
public IObservable<IEnumerable<Exception>> Exceptions => Observable.Return(Enumerable.Empty<Exception>());
|
protected SourceList<Exception> Exceptions { get; } = new();
|
||||||
|
IObservable<IChangeSet<Exception>> IItem.Exceptions => Exceptions.Connect();
|
||||||
|
|
||||||
ReadOnlyExtensionCollection IItem.Extensions => _extensions;
|
ReadOnlyExtensionCollection IItem.Extensions => _extensions;
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public record Container(
|
|||||||
IContentProvider Provider,
|
IContentProvider Provider,
|
||||||
bool AllowRecursiveDeletion,
|
bool AllowRecursiveDeletion,
|
||||||
PointInTime PointInTime,
|
PointInTime PointInTime,
|
||||||
IObservable<IEnumerable<Exception>> Exceptions,
|
IObservable<IChangeSet<Exception>> Exceptions,
|
||||||
ReadOnlyExtensionCollection Extensions,
|
ReadOnlyExtensionCollection Extensions,
|
||||||
IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> Items) : IContainer
|
IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> Items) : IContainer
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using DynamicData;
|
||||||
using FileTime.Core.ContentAccess;
|
using FileTime.Core.ContentAccess;
|
||||||
using FileTime.Core.Enums;
|
using FileTime.Core.Enums;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
@@ -18,7 +19,7 @@ public record Element(
|
|||||||
string? Attributes,
|
string? Attributes,
|
||||||
IContentProvider Provider,
|
IContentProvider Provider,
|
||||||
PointInTime PointInTime,
|
PointInTime PointInTime,
|
||||||
IObservable<IEnumerable<Exception>> Exceptions,
|
IObservable<IChangeSet<Exception>> Exceptions,
|
||||||
ReadOnlyExtensionCollection Extensions) : IElement
|
ReadOnlyExtensionCollection Extensions) : IElement
|
||||||
{
|
{
|
||||||
public AbsolutePathType Type => AbsolutePathType.Element;
|
public AbsolutePathType Type => AbsolutePathType.Element;
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ namespace FileTime.Core.Services;
|
|||||||
public class Tab : ITab
|
public class Tab : ITab
|
||||||
{
|
{
|
||||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||||
|
private readonly ITabEvents _tabEvents;
|
||||||
private readonly BehaviorSubject<IContainer?> _currentLocation = new(null);
|
private readonly BehaviorSubject<IContainer?> _currentLocation = new(null);
|
||||||
private readonly BehaviorSubject<IContainer?> _currentLocationForced = new(null);
|
private readonly BehaviorSubject<IContainer?> _currentLocationForced = new(null);
|
||||||
private readonly BehaviorSubject<AbsolutePath?> _currentSelectedItem = new(null);
|
private readonly BehaviorSubject<AbsolutePath?> _currentSelectedItem = new(null);
|
||||||
@@ -24,9 +25,10 @@ public class Tab : ITab
|
|||||||
public IObservable<AbsolutePath?> CurrentSelectedItem { get; }
|
public IObservable<AbsolutePath?> CurrentSelectedItem { get; }
|
||||||
public FullName? LastDeepestSelectedPath { get; private set; }
|
public FullName? LastDeepestSelectedPath { get; private set; }
|
||||||
|
|
||||||
public Tab(ITimelessContentProvider timelessContentProvider)
|
public Tab(ITimelessContentProvider timelessContentProvider, ITabEvents tabEvents)
|
||||||
{
|
{
|
||||||
_timelessContentProvider = timelessContentProvider;
|
_timelessContentProvider = timelessContentProvider;
|
||||||
|
_tabEvents = tabEvents;
|
||||||
_currentPointInTime = null!;
|
_currentPointInTime = null!;
|
||||||
|
|
||||||
_timelessContentProvider.CurrentPointInTime.Subscribe(p => _currentPointInTime = p);
|
_timelessContentProvider.CurrentPointInTime.Subscribe(p => _currentPointInTime = p);
|
||||||
@@ -60,7 +62,7 @@ public class Tab : ITab
|
|||||||
),
|
),
|
||||||
CurrentLocation
|
CurrentLocation
|
||||||
.Where(c => c is null)
|
.Where(c => c is null)
|
||||||
.Select(_ => (IObservable<IChangeSet<IItem, string>>?)null)
|
.Select(_ => (IObservable<IChangeSet<IItem, string>>?) null)
|
||||||
)
|
)
|
||||||
.Publish(null)
|
.Publish(null)
|
||||||
.RefCount();
|
.RefCount();
|
||||||
@@ -137,8 +139,25 @@ public class Tab : ITab
|
|||||||
return newSelectedItem;
|
return newSelectedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetCurrentLocation(IContainer newLocation) => _currentLocation.OnNext(newLocation);
|
public void SetCurrentLocation(IContainer newLocation)
|
||||||
public void ForceSetCurrentLocation(IContainer newLocation) => _currentLocationForced.OnNext(newLocation);
|
{
|
||||||
|
_currentLocation.OnNext(newLocation);
|
||||||
|
|
||||||
|
if (newLocation.FullName != null)
|
||||||
|
{
|
||||||
|
_tabEvents.OnLocationChanged(this, newLocation.FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ForceSetCurrentLocation(IContainer newLocation)
|
||||||
|
{
|
||||||
|
_currentLocationForced.OnNext(newLocation);
|
||||||
|
|
||||||
|
if (newLocation.FullName != null)
|
||||||
|
{
|
||||||
|
_tabEvents.OnLocationChanged(this, newLocation.FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void SetSelectedItem(AbsolutePath newSelectedItem) => _currentSelectedItem.OnNext(newSelectedItem);
|
public void SetSelectedItem(AbsolutePath newSelectedItem) => _currentSelectedItem.OnNext(newSelectedItem);
|
||||||
|
|
||||||
|
|||||||
13
src/Core/FileTime.Core.Services/TabEvents.cs
Normal file
13
src/Core/FileTime.Core.Services/TabEvents.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
|
namespace FileTime.Core.Services;
|
||||||
|
|
||||||
|
public class TabEvents : ITabEvents
|
||||||
|
{
|
||||||
|
public event EventHandler<TabLocationChanged> LocationChanged;
|
||||||
|
|
||||||
|
public void OnLocationChanged(ITab tab, FullName location)
|
||||||
|
{
|
||||||
|
LocationChanged?.Invoke(this, new TabLocationChanged(location, tab));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,6 +63,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.GuiApp.Font", "Gui
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.GuiApp.Font.Abstractions", "GuiApp\Avalonia\FileTime.GuiApp.Font.Abstractions\FileTime.GuiApp.Font.Abstractions.csproj", "{2D07F149-106B-4644-9586-D6218F78D868}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.GuiApp.Font.Abstractions", "GuiApp\Avalonia\FileTime.GuiApp.Font.Abstractions\FileTime.GuiApp.Font.Abstractions.csproj", "{2D07F149-106B-4644-9586-D6218F78D868}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.FrequencyNavigation", "AppCommon\FileTime.App.FrequencyNavigation\FileTime.App.FrequencyNavigation.csproj", "{253348AD-C9C0-4162-A2ED-C6FF8730B275}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.FrequencyNavigation.Abstractions", "AppCommon\FileTime.App.FrequencyNavigation.Abstractions\FileTime.App.FrequencyNavigation.Abstractions.csproj", "{C1CA8B7E-F8E6-40AB-A45B-5EBEF6996290}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -157,6 +161,14 @@ Global
|
|||||||
{2D07F149-106B-4644-9586-D6218F78D868}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{2D07F149-106B-4644-9586-D6218F78D868}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{2D07F149-106B-4644-9586-D6218F78D868}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{2D07F149-106B-4644-9586-D6218F78D868}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{2D07F149-106B-4644-9586-D6218F78D868}.Release|Any CPU.Build.0 = Release|Any CPU
|
{2D07F149-106B-4644-9586-D6218F78D868}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{253348AD-C9C0-4162-A2ED-C6FF8730B275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{253348AD-C9C0-4162-A2ED-C6FF8730B275}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{253348AD-C9C0-4162-A2ED-C6FF8730B275}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{253348AD-C9C0-4162-A2ED-C6FF8730B275}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C1CA8B7E-F8E6-40AB-A45B-5EBEF6996290}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C1CA8B7E-F8E6-40AB-A45B-5EBEF6996290}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C1CA8B7E-F8E6-40AB-A45B-5EBEF6996290}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C1CA8B7E-F8E6-40AB-A45B-5EBEF6996290}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -185,6 +197,8 @@ Global
|
|||||||
{9B161766-A672-4D59-B591-C68907905158} = {3324D046-1E05-46B5-B1BA-82910D56B332}
|
{9B161766-A672-4D59-B591-C68907905158} = {3324D046-1E05-46B5-B1BA-82910D56B332}
|
||||||
{767F3868-11D0-445D-9B86-F81C7FCEB6FA} = {01F231DE-4A65-435F-B4BB-77EE5221890C}
|
{767F3868-11D0-445D-9B86-F81C7FCEB6FA} = {01F231DE-4A65-435F-B4BB-77EE5221890C}
|
||||||
{2D07F149-106B-4644-9586-D6218F78D868} = {01F231DE-4A65-435F-B4BB-77EE5221890C}
|
{2D07F149-106B-4644-9586-D6218F78D868} = {01F231DE-4A65-435F-B4BB-77EE5221890C}
|
||||||
|
{253348AD-C9C0-4162-A2ED-C6FF8730B275} = {A5291117-3001-498B-AC8B-E14F71F72570}
|
||||||
|
{C1CA8B7E-F8E6-40AB-A45B-5EBEF6996290} = {A5291117-3001-498B-AC8B-E14F71F72570}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ public static class MainConfiguration
|
|||||||
new(EnterRapidTravelCommand.CommandName,new KeyConfig(Key.OemQuestion, shift: true)),
|
new(EnterRapidTravelCommand.CommandName,new KeyConfig(Key.OemQuestion, shift: true)),
|
||||||
//new CommandBindingConfiguration(ConfigCommand.FindByName, new[] { Key.F, Key.N }),
|
//new CommandBindingConfiguration(ConfigCommand.FindByName, new[] { Key.F, Key.N }),
|
||||||
//new CommandBindingConfiguration(ConfigCommand.FindByNameRegex, new[] { Key.F, Key.R }),
|
//new CommandBindingConfiguration(ConfigCommand.FindByNameRegex, new[] { Key.F, Key.R }),
|
||||||
|
new(GoByFrequencyCommand.CommandName, Key.Z),
|
||||||
new(GoToHomeCommand.CommandName, new[] { Key.G, Key.H }),
|
new(GoToHomeCommand.CommandName, new[] { Key.G, Key.H }),
|
||||||
new(GoToPathCommand.CommandName, new KeyConfig(Key.L, ctrl: true)),
|
new(GoToPathCommand.CommandName, new KeyConfig(Key.L, ctrl: true)),
|
||||||
new(GoToPathCommand.CommandName, new[] { Key.G, Key.P }),
|
new(GoToPathCommand.CommandName, new[] { Key.G, Key.P }),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using Avalonia;
|
|||||||
using Avalonia.Controls.ApplicationLifetimes;
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
using FileTime.App.DependencyInjection;
|
using FileTime.App.DependencyInjection;
|
||||||
|
using FileTime.App.FrequencyNavigation;
|
||||||
using FileTime.GuiApp.Font;
|
using FileTime.GuiApp.Font;
|
||||||
using FileTime.GuiApp.ViewModels;
|
using FileTime.GuiApp.ViewModels;
|
||||||
using FileTime.GuiApp.Views;
|
using FileTime.GuiApp.Views;
|
||||||
@@ -17,6 +18,7 @@ public partial class App : Application
|
|||||||
var configuration = Startup.CreateConfiguration();
|
var configuration = Startup.CreateConfiguration();
|
||||||
DI.ServiceProvider = DependencyInjection
|
DI.ServiceProvider = DependencyInjection
|
||||||
.RegisterDefaultServices()
|
.RegisterDefaultServices()
|
||||||
|
.AddFrequencyNavigation()
|
||||||
.AddConfiguration(configuration)
|
.AddConfiguration(configuration)
|
||||||
.ConfigureFont(configuration)
|
.ConfigureFont(configuration)
|
||||||
.RegisterLogging()
|
.RegisterLogging()
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
|
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
|
||||||
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
|
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.DependencyInjection\FileTime.App.DependencyInjection.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.FrequencyNavigation\FileTime.App.FrequencyNavigation.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.GuiApp.CustomImpl\FileTime.GuiApp.CustomImpl.csproj" />
|
<ProjectReference Include="..\FileTime.GuiApp.CustomImpl\FileTime.GuiApp.CustomImpl.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.GuiApp.Font\FileTime.GuiApp.Font.csproj" />
|
<ProjectReference Include="..\FileTime.GuiApp.Font\FileTime.GuiApp.Font.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.GuiApp\FileTime.GuiApp.csproj" />
|
<ProjectReference Include="..\FileTime.GuiApp\FileTime.GuiApp.csproj" />
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.FrequencyNavigation.Abstractions\FileTime.App.FrequencyNavigation.Abstractions.csproj" />
|
||||||
<ProjectReference Include="..\..\..\Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj" />
|
<ProjectReference Include="..\..\..\Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.GuiApp.Abstractions\FileTime.GuiApp.Abstractions.csproj" />
|
<ProjectReference Include="..\FileTime.GuiApp.Abstractions\FileTime.GuiApp.Abstractions.csproj" />
|
||||||
<ProjectReference Include="..\FileTime.GuiApp.Font.Abstractions\FileTime.GuiApp.Font.Abstractions.csproj" />
|
<ProjectReference Include="..\FileTime.GuiApp.Font.Abstractions\FileTime.GuiApp.Font.Abstractions.csproj" />
|
||||||
|
|||||||
@@ -1,20 +1,31 @@
|
|||||||
using DynamicData;
|
using DynamicData;
|
||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.App.Core.ViewModels;
|
using FileTime.App.Core.ViewModels;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace FileTime.GuiApp.Services;
|
namespace FileTime.GuiApp.Services;
|
||||||
|
|
||||||
public class ModalService : IModalService
|
public class ModalService : IModalService
|
||||||
{
|
{
|
||||||
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly SourceList<IModalViewModel> _openModals = new();
|
private readonly SourceList<IModalViewModel> _openModals = new();
|
||||||
public IObservable<IChangeSet<IModalViewModel>> OpenModals { get; }
|
public IObservable<IChangeSet<IModalViewModel>> OpenModals { get; }
|
||||||
|
|
||||||
public ModalService()
|
public ModalService(IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
|
_serviceProvider = serviceProvider;
|
||||||
OpenModals = _openModals.Connect().StartWithEmpty();
|
OpenModals = _openModals.Connect().StartWithEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OpenModal(IModalViewModel modalToOpen) => _openModals.Add(modalToOpen);
|
public void OpenModal(IModalViewModel modalToOpen) => _openModals.Add(modalToOpen);
|
||||||
|
|
||||||
public void CloseModal(IModalViewModel modalToClose) => _openModals.Remove(modalToClose);
|
public void CloseModal(IModalViewModel modalToClose) => _openModals.Remove(modalToClose);
|
||||||
|
|
||||||
|
public T OpenModal<T>() where T : IModalViewModel
|
||||||
|
{
|
||||||
|
var modal = _serviceProvider.GetRequiredService<T>();
|
||||||
|
OpenModal(modal);
|
||||||
|
|
||||||
|
return modal;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,8 @@ using System.Reflection;
|
|||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using FileTime.App.Core.Services;
|
using FileTime.App.Core.Services;
|
||||||
using FileTime.App.Core.UserCommand;
|
using FileTime.App.Core.UserCommand;
|
||||||
|
using FileTime.App.FrequencyNavigation.Services;
|
||||||
|
using FileTime.App.FrequencyNavigation.ViewModels;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
using FileTime.GuiApp.Services;
|
using FileTime.GuiApp.Services;
|
||||||
@@ -24,6 +26,7 @@ namespace FileTime.GuiApp.ViewModels;
|
|||||||
[Inject(typeof(IDialogService), PropertyAccessModifier = AccessModifier.Public)]
|
[Inject(typeof(IDialogService), PropertyAccessModifier = AccessModifier.Public)]
|
||||||
[Inject(typeof(ITimelessContentProvider), PropertyName = "_timelessContentProvider")]
|
[Inject(typeof(ITimelessContentProvider), PropertyName = "_timelessContentProvider")]
|
||||||
[Inject(typeof(IFontService), "_fontService")]
|
[Inject(typeof(IFontService), "_fontService")]
|
||||||
|
[Inject(typeof(IFrequencyNavigationService), PropertyAccessModifier = AccessModifier.Public)]
|
||||||
public partial class MainWindowViewModel : IMainWindowViewModelBase
|
public partial class MainWindowViewModel : IMainWindowViewModelBase
|
||||||
{
|
{
|
||||||
public bool Loading => false;
|
public bool Loading => false;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="FileTime.GuiApp.Views.FrequencyNavigation"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:viewModels="clr-namespace:FileTime.App.FrequencyNavigation.ViewModels;assembly=FileTime.App.FrequencyNavigation.Abstractions"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="viewModels:IFrequencyNavigationViewModel"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid RowDefinitions="Auto,*">
|
||||||
|
<TextBox
|
||||||
|
KeyDown="Search_OnKeyDown"
|
||||||
|
Text="{Binding SearchText, Mode=TwoWay}" />
|
||||||
|
<ItemsRepeater
|
||||||
|
Grid.Row="1"
|
||||||
|
Items="{Binding FilteredMatches}">
|
||||||
|
<ItemsRepeater.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="x:String">
|
||||||
|
<Grid Margin="5">
|
||||||
|
<TextBlock Text="{Binding}" />
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsRepeater.ItemTemplate>
|
||||||
|
</ItemsRepeater>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using FileTime.App.FrequencyNavigation.ViewModels;
|
||||||
|
|
||||||
|
namespace FileTime.GuiApp.Views;
|
||||||
|
|
||||||
|
public partial class FrequencyNavigation : UserControl
|
||||||
|
{
|
||||||
|
public FrequencyNavigation()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Search_OnKeyDown(object? sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is not IFrequencyNavigationViewModel viewModel) return;
|
||||||
|
|
||||||
|
if (e.Key == Key.Escape)
|
||||||
|
{
|
||||||
|
viewModel.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -709,6 +709,15 @@
|
|||||||
</Border>
|
</Border>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
<Border
|
||||||
|
DataContext="{Binding FrequencyNavigationService.CurrentModal}"
|
||||||
|
Margin="100"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Background="{DynamicResource BarelyTransparentBackgroundColor}"
|
||||||
|
IsVisible="{Binding ShowWindow^, FallbackValue=False}">
|
||||||
|
<local:FrequencyNavigation />
|
||||||
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid IsVisible="{Binding Loading}">
|
<Grid IsVisible="{Binding Loading}">
|
||||||
|
|||||||
@@ -76,11 +76,11 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
{
|
{
|
||||||
if ((path?.Length ?? 0) == 0)
|
if ((path?.Length ?? 0) == 0)
|
||||||
{
|
{
|
||||||
return Task.FromResult((IItem)this);
|
return Task.FromResult((IItem) this);
|
||||||
}
|
}
|
||||||
else if (Directory.Exists(path))
|
else if (Directory.Exists(path))
|
||||||
{
|
{
|
||||||
return Task.FromResult((IItem)DirectoryToContainer(
|
return Task.FromResult((IItem) DirectoryToContainer(
|
||||||
new DirectoryInfo(path!.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar),
|
new DirectoryInfo(path!.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar),
|
||||||
pointInTime,
|
pointInTime,
|
||||||
!itemInitializationSettings.SkipChildInitialization)
|
!itemInitializationSettings.SkipChildInitialization)
|
||||||
@@ -88,7 +88,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
}
|
}
|
||||||
else if (File.Exists(path))
|
else if (File.Exists(path))
|
||||||
{
|
{
|
||||||
return Task.FromResult((IItem)FileToElement(new FileInfo(path), pointInTime));
|
return Task.FromResult((IItem) FileToElement(new FileInfo(path), pointInTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
var type = forceResolvePathType switch
|
var type = forceResolvePathType switch
|
||||||
@@ -120,10 +120,10 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
return forceResolvePathType switch
|
return forceResolvePathType switch
|
||||||
{
|
{
|
||||||
AbsolutePathType.Container => Task.FromResult(
|
AbsolutePathType.Container => Task.FromResult(
|
||||||
(IItem)CreateEmptyContainer(
|
(IItem) CreateEmptyContainer(
|
||||||
nativePath,
|
nativePath,
|
||||||
pointInTime,
|
pointInTime,
|
||||||
Observable.Return(new List<Exception>() { innerException })
|
new List<Exception>() {innerException}
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
AbsolutePathType.Element => Task.FromResult(CreateEmptyElement(nativePath)),
|
AbsolutePathType.Element => Task.FromResult(CreateEmptyElement(nativePath)),
|
||||||
@@ -135,9 +135,14 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
|
|
||||||
private Container CreateEmptyContainer(NativePath nativePath,
|
private Container CreateEmptyContainer(NativePath nativePath,
|
||||||
PointInTime pointInTime,
|
PointInTime pointInTime,
|
||||||
IObservable<IEnumerable<Exception>>? exceptions = null)
|
IEnumerable<Exception>? initialExceptions = null)
|
||||||
{
|
{
|
||||||
var nonNullExceptions = exceptions ?? Observable.Return(Enumerable.Empty<Exception>());
|
var exceptions = new SourceList<Exception>();
|
||||||
|
if (initialExceptions is not null)
|
||||||
|
{
|
||||||
|
exceptions.AddRange(initialExceptions);
|
||||||
|
}
|
||||||
|
|
||||||
var name = nativePath.Path.Split(Path.DirectorySeparatorChar).LastOrDefault() ?? "???";
|
var name = nativePath.Path.Split(Path.DirectorySeparatorChar).LastOrDefault() ?? "???";
|
||||||
var fullName = GetFullName(nativePath);
|
var fullName = GetFullName(nativePath);
|
||||||
|
|
||||||
@@ -166,7 +171,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
this,
|
this,
|
||||||
true,
|
true,
|
||||||
pointInTime,
|
pointInTime,
|
||||||
nonNullExceptions,
|
exceptions.Connect(),
|
||||||
new ExtensionCollection().AsReadOnly(),
|
new ExtensionCollection().AsReadOnly(),
|
||||||
Observable.Return<IObservable<IChangeSet<AbsolutePath, string>>?>(null)
|
Observable.Return<IObservable<IChangeSet<AbsolutePath, string>>?>(null)
|
||||||
);
|
);
|
||||||
@@ -202,7 +207,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
pointInTime,
|
pointInTime,
|
||||||
parentFullName,
|
parentFullName,
|
||||||
AbsolutePathType.Container);
|
AbsolutePathType.Container);
|
||||||
var exceptions = new BehaviorSubject<IEnumerable<Exception>>(Enumerable.Empty<Exception>());
|
var exceptions = new SourceList<Exception>();
|
||||||
|
|
||||||
var children = new SourceCache<AbsolutePath, string>(i => i.Path.Path);
|
var children = new SourceCache<AbsolutePath, string>(i => i.Path.Path);
|
||||||
|
|
||||||
@@ -221,14 +226,14 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
this,
|
this,
|
||||||
true,
|
true,
|
||||||
pointInTime,
|
pointInTime,
|
||||||
exceptions,
|
exceptions.Connect(),
|
||||||
new ExtensionCollection().AsReadOnly(),
|
new ExtensionCollection().AsReadOnly(),
|
||||||
//Observable.FromAsync(async () => await Task.Run(InitChildrenHelper)
|
//Observable.FromAsync(async () => await Task.Run(InitChildrenHelper)
|
||||||
//Observable.Return(InitChildren())
|
//Observable.Return(InitChildren())
|
||||||
Observable.Return(children.Connect())
|
Observable.Return(children.Connect())
|
||||||
);
|
);
|
||||||
|
|
||||||
Task.Run(() => LoadChildren(container, directoryInfo, children, pointInTime));
|
Task.Run(() => LoadChildren(container, directoryInfo, children, pointInTime, exceptions));
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
|
|
||||||
@@ -241,25 +246,25 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
var items = GetItemsByContainer(directoryInfo, pointInTime);
|
var items = GetItemsByContainer(directoryInfo, pointInTime);
|
||||||
var result = new SourceCache<AbsolutePath, string>(i => i.Path.Path);
|
var result = new SourceCache<AbsolutePath, string>(i => i.Path.Path);
|
||||||
|
|
||||||
if (items.Count == 0) return (IObservable<IChangeSet<AbsolutePath, string>>?)result.Connect().StartWithEmpty();
|
if (items.Count == 0) return (IObservable<IChangeSet<AbsolutePath, string>>?) result.Connect().StartWithEmpty();
|
||||||
|
|
||||||
result.AddOrUpdate(items);
|
result.AddOrUpdate(items);
|
||||||
return (IObservable<IChangeSet<AbsolutePath, string>>?)result.Connect();
|
return (IObservable<IChangeSet<AbsolutePath, string>>?) result.Connect();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
exceptions.OnNext(new List<Exception> { e });
|
exceptions.Add(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadChildren(
|
private void LoadChildren(Container container,
|
||||||
Container container,
|
|
||||||
DirectoryInfo directoryInfo,
|
DirectoryInfo directoryInfo,
|
||||||
SourceCache<AbsolutePath, string> children,
|
SourceCache<AbsolutePath, string> children,
|
||||||
PointInTime pointInTime)
|
PointInTime pointInTime,
|
||||||
|
SourceList<Exception> exceptions)
|
||||||
{
|
{
|
||||||
var lockobj = new object();
|
var lockobj = new object();
|
||||||
var loadingIndicatorCancellation = new CancellationTokenSource();
|
var loadingIndicatorCancellation = new CancellationTokenSource();
|
||||||
@@ -274,6 +279,8 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LoadChildren()
|
void LoadChildren()
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (var directory in directoryInfo.EnumerateDirectories())
|
foreach (var directory in directoryInfo.EnumerateDirectories())
|
||||||
{
|
{
|
||||||
@@ -289,6 +296,11 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
children.AddOrUpdate(absolutePath);
|
children.AddOrUpdate(absolutePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
exceptions.Add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async Task DelayedLoadingIndicator()
|
async Task DelayedLoadingIndicator()
|
||||||
{
|
{
|
||||||
@@ -297,7 +309,9 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
{
|
{
|
||||||
await Task.Delay(500, token);
|
await Task.Delay(500, token);
|
||||||
}
|
}
|
||||||
catch { }
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
lock (lockobj)
|
lock (lockobj)
|
||||||
{
|
{
|
||||||
@@ -345,7 +359,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
GetFileAttributes(fileInfo),
|
GetFileAttributes(fileInfo),
|
||||||
this,
|
this,
|
||||||
pointInTime,
|
pointInTime,
|
||||||
Observable.Return(Enumerable.Empty<Exception>()),
|
new SourceList<Exception>().Connect(),
|
||||||
extensions.AsReadOnly()
|
extensions.AsReadOnly()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -385,7 +399,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
|
|||||||
var size = maxLength ?? realFileSize switch
|
var size = maxLength ?? realFileSize switch
|
||||||
{
|
{
|
||||||
> int.MaxValue => int.MaxValue,
|
> int.MaxValue => int.MaxValue,
|
||||||
_ => (int)realFileSize
|
_ => (int) realFileSize
|
||||||
};
|
};
|
||||||
var buffer = new byte[size];
|
var buffer = new byte[size];
|
||||||
await reader.ReadAsync(buffer.AsMemory(0, size), cancellationToken);
|
await reader.ReadAsync(buffer.AsMemory(0, size), cancellationToken);
|
||||||
|
|||||||
Reference in New Issue
Block a user