Top navigation, search delete

This commit is contained in:
2023-07-28 17:10:50 +02:00
parent ee42e38e45
commit ed6864e127
27 changed files with 320 additions and 149 deletions

View File

@@ -15,32 +15,17 @@ public class TabPersistenceService : ITabPersistenceService
private readonly IAppState _appState;
private readonly ILogger<TabPersistenceService> _logger;
private class PersistenceRoot
//TODO: make this a configuration maybe?
private readonly List<string> _contentProvidersNotToRestore = new()
{
public TabStates? TabStates { get; set; }
}
"search"
};
private class TabStates
{
public List<TabState>? Tabs { get; set; }
public int? ActiveTabNumber { get; set; }
}
private record PersistenceRoot(TabStates? TabStates);
private class TabState
{
public string? Path { get; set; }
public int Number { get; set; }
private record TabStates(List<TabState>? Tabs, int? ActiveTabNumber);
public TabState()
{
}
public TabState(FullName path, int number)
{
Path = path.Path;
Number = number;
}
}
private record TabState(string? Path, int Number);
private readonly string _settingsPath;
private readonly JsonSerializerOptions _jsonOptions;
@@ -149,7 +134,7 @@ public class TabPersistenceService : ITabPersistenceService
}
catch
{
path = path.GetParent();
path = path?.GetParent();
if (path == null)
{
throw new Exception($"Could not find an initializable path along {tab.Path}");
@@ -159,6 +144,8 @@ public class TabPersistenceService : ITabPersistenceService
if (container == null) continue;
if (_contentProvidersNotToRestore.Contains(container.Provider.Name)) continue;
var tabToLoad = await _serviceProvider.GetAsyncInitableResolver(container)
.GetRequiredServiceAsync<ITab>();
var tabViewModel = _serviceProvider.GetInitableResolver(tabToLoad, tab.Number)
@@ -181,7 +168,15 @@ public class TabPersistenceService : ITabPersistenceService
if (_appState.Tabs.Count == 0) return false;
var tabToActivate = _appState.Tabs.FirstOrDefault(t => t.TabNumber == tabStates.ActiveTabNumber);
var optimalTabs = _appState
.Tabs
.TakeWhile(t => t.TabNumber <= tabStates.ActiveTabNumber)
.Reverse();
var suboptimalTabs = _appState
.Tabs
.SkipWhile(t => t.TabNumber <= tabStates.ActiveTabNumber);
var tabToActivate = optimalTabs.Concat(suboptimalTabs).FirstOrDefault();
if (tabToActivate is not null) _appState.SetSelectedTab(tabToActivate);
return true;
@@ -189,10 +184,8 @@ public class TabPersistenceService : ITabPersistenceService
public void SaveStates(CancellationToken token = default)
{
var state = new PersistenceRoot
{
TabStates = SerializeTabStates()
};
var state = new PersistenceRoot(SerializeTabStates());
var settingsDirectory = new DirectoryInfo(string.Join(Path.DirectorySeparatorChar,
_settingsPath.Split(Path.DirectorySeparatorChar)[0..^1]));
if (!settingsDirectory.Exists) settingsDirectory.Create();
@@ -207,16 +200,15 @@ public class TabPersistenceService : ITabPersistenceService
{
var currentLocation = tab.CurrentLocation.Value;
if (currentLocation is null) continue;
tabStates.Add(new TabState(currentLocation.FullName!, tab.TabNumber));
tabStates.Add(new TabState(currentLocation.FullName!.Path, tab.TabNumber));
}
return new TabStates
{
Tabs = tabStates,
ActiveTabNumber = _appState.CurrentSelectedTab?.TabNumber
};
return new TabStates(
tabStates,
_appState.CurrentSelectedTab?.TabNumber
);
}
public async Task InitAsync()
public async Task InitAsync()
=> await LoadStatesAsync();
}

View File

@@ -10,7 +10,6 @@ using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels;
using FileTime.Core.Command;
using FileTime.Core.Command.CreateContainer;
using FileTime.Core.Command.CreateElement;
using FileTime.Core.Command.Move;
using FileTime.Core.Extensions;
using FileTime.Core.Interactions;

View File

@@ -15,7 +15,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
{
private readonly ISystemClipboardService _systemClipboardService;
private readonly IUserCommunicationService _userCommunicationService;
private readonly ISearchManager _searchManager;
private readonly ISearchContentProvider _searchContentProvider;
private readonly IItemNameConverterService _itemNameConverterService;
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IUserCommandHandlerService _userCommandHandlerService;
@@ -28,7 +28,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
IAppState appState,
ISystemClipboardService systemClipboardService,
IUserCommunicationService userCommunicationService,
ISearchManager searchManager,
ISearchContentProvider searchContentProvider,
IItemNameConverterService itemNameConverterService,
ITimelessContentProvider timelessContentProvider,
IUserCommandHandlerService userCommandHandlerService,
@@ -36,7 +36,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
{
_systemClipboardService = systemClipboardService;
_userCommunicationService = userCommunicationService;
_searchManager = searchManager;
_searchContentProvider = searchContentProvider;
_itemNameConverterService = itemNameConverterService;
_timelessContentProvider = timelessContentProvider;
_userCommandHandlerService = userCommandHandlerService;
@@ -113,7 +113,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
_ => throw new ArgumentOutOfRangeException()
};
var searchTask = await _searchManager.StartSearchAsync(searchMatcher, _currentLocation.Value);
var searchTask = await _searchContentProvider.StartSearchAsync(searchMatcher, _currentLocation.Value);
var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, searchTask.SearchContainer));
await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
}

View File

@@ -52,7 +52,7 @@ public abstract partial class ItemViewModel : IItemViewModel
var displayName = itemViewModelType switch
{
ItemViewModelType.Main => _appState.RapidTravelText.Map(s => (IReadOnlyList<ItemNamePart>) _itemNameConverterService.GetDisplayName(item.DisplayName, s)),
_ => new DeclarativeProperty<IReadOnlyList<ItemNamePart>>(new List<ItemNamePart> {new (item.DisplayName)}),
_ => new DeclarativeProperty<IReadOnlyList<ItemNamePart>>(new List<ItemNamePart> {new(item.DisplayName)}),
};
BaseItem = item;
@@ -103,6 +103,6 @@ public abstract partial class ItemViewModel : IItemViewModel
var deepestPath = _parentTab.Tab.LastDeepestSelectedPath;
var commonPath = FullName.CreateSafe(PathHelper.GetCommonPath(ownFullName.Path, deepestPath.Path));
return commonPath.Path == ownFullName.Path;
return commonPath is not null && commonPath.Path == ownFullName.Path;
}
}

View File

@@ -198,7 +198,7 @@ public partial class TabViewModel : ITabViewModel
private static async Task<IItem> MapItemAsync(AbsolutePath item)
=> await item.ResolveAsync(forceResolve: true,
itemInitializationSettings: new ItemInitializationSettings(true));
itemInitializationSettings: new ItemInitializationSettings {SkipChildInitialization = true});
private IItemViewModel MapItemToViewModel(IItem item, ItemViewModelType type)
{

View File

@@ -31,6 +31,7 @@ public static class DependencyInjection
serviceCollection.TryAddSingleton<ICommandRunner, CommandRunner>();
serviceCollection.TryAddSingleton<IContentAccessorFactory, ContentAccessorFactory>();
serviceCollection.TryAddSingleton<IContentProviderRegistry, ContentProviderRegistry>();
serviceCollection.TryAddSingleton<IRootContentProvider, RootContentProvider>();
//TODO: check local/remote context
serviceCollection.TryAddSingleton<ILocalCommandExecutor, LocalCommandExecutor>();
serviceCollection.TryAddSingleton<ICommandSchedulerNotifier, LocalCommandSchedulerNotifier>();

View File

@@ -1,8 +1,10 @@
using FileTime.Core.ContentAccess;
using FileTime.Core.Models;
namespace FileTime.App.Search;
public interface ISearchContentProvider : IContentProvider
{
Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn);
void RemoveSearch(FullName searchFullName);
}

View File

@@ -1,9 +0,0 @@
using FileTime.Core.Models;
namespace FileTime.App.Search;
public interface ISearchManager
{
Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn);
IReadOnlyList<ISearchTask> SearchTasks { get; }
}

View File

@@ -7,12 +7,14 @@ namespace FileTime.App.Search;
public class SearchContentProvider : ContentProviderBase, ISearchContentProvider
{
private readonly ISearchManager _searchManager;
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly List<SearchTask> _searchTasks = new();
public const string ContentProviderName = "search";
public SearchContentProvider(ISearchManager searchManager) : base(ContentProviderName)
public SearchContentProvider(ITimelessContentProvider timelessContentProvider)
: base(ContentProviderName, timelessContentProvider)
{
_searchManager = searchManager;
_timelessContentProvider = timelessContentProvider;
}
public override Task<IItem> GetItemByNativePathAsync(
@@ -21,9 +23,12 @@ public class SearchContentProvider : ContentProviderBase, ISearchContentProvider
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default
) =>
Task.FromResult((IItem) _searchManager.SearchTasks
)
{
if (nativePath.Path == ContentProviderName) return Task.FromResult((IItem) this);
return Task.FromResult((IItem) _searchTasks
.First(searchTask => searchTask.SearchContainer.NativePath == nativePath).SearchContainer);
}
public override NativePath GetNativePath(FullName fullName) => new(fullName.Path);
public override FullName GetFullName(NativePath nativePath) => new(nativePath.Path);
@@ -36,4 +41,34 @@ public class SearchContentProvider : ContentProviderBase, ISearchContentProvider
=> Task.FromResult(null as byte[]);
public override bool CanHandlePath(NativePath path) => path.Path.StartsWith(ContentProviderName);
public async Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn)
{
var searchTask = new SearchTask(
searchIn,
this,
_timelessContentProvider,
matcher,
new AbsolutePath(_timelessContentProvider, this)
);
_searchTasks.Add(searchTask);
await searchTask.StartAsync();
Items.Add(new AbsolutePath(_timelessContentProvider, searchTask.SearchContainer));
return searchTask;
}
public void RemoveSearch(FullName searchFullName)
{
var searchTask = _searchTasks.FirstOrDefault(t => t.SearchContainer.FullName == searchFullName);
if (searchTask is null) return;
_searchTasks.Remove(searchTask);
var searchItem = Items.FirstOrDefault(c => c.Path == searchTask.SearchContainer.FullName);
if (searchItem is not null)
{
Items.Remove(searchItem);
}
}
}

View File

@@ -0,0 +1,13 @@
using FileTime.Core.ContentAccess;
using FileTime.Core.Models;
namespace FileTime.App.Search;
public class SearchItemDeleter : IItemDeleter<ISearchContentProvider>
{
public Task DeleteAsync(ISearchContentProvider contentProvider, FullName fullName)
{
contentProvider.RemoveSearch(fullName);
return Task.CompletedTask;
}
}

View File

@@ -1,35 +0,0 @@
using FileTime.Core.Models;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.App.Search;
public class SearchManager : ISearchManager
{
private readonly IServiceProvider _serviceProvider;
private ISearchContentProvider? _searchContainerProvider;
private readonly List<SearchTask> _searchTasks = new();
public IReadOnlyList<ISearchTask> SearchTasks { get; }
public SearchManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
SearchTasks = _searchTasks.AsReadOnly();
}
public async Task<ISearchTask> StartSearchAsync(ISearchMatcher matcher, IContainer searchIn)
{
_searchContainerProvider ??= _serviceProvider.GetRequiredService<ISearchContentProvider>();
var searchTask = new SearchTask(
searchIn,
_searchContainerProvider,
matcher
);
_searchTasks.Add(searchTask);
await searchTask.StartAsync();
return searchTask;
}
}

View File

@@ -10,6 +10,7 @@ namespace FileTime.App.Search;
public class SearchTask : ISearchTask
{
private readonly IContainer _baseContainer;
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ISearchMatcher _matcher;
private readonly Container _container;
private readonly ObservableCollection<Exception> _exceptions = new();
@@ -22,19 +23,22 @@ public class SearchTask : ISearchTask
public SearchTask(
IContainer baseContainer,
IContentProvider contentProvider,
ISearchMatcher matcher
ISearchContentProvider contentProvider,
ITimelessContentProvider timelessContentProvider,
ISearchMatcher matcher,
AbsolutePath parent
)
{
var randomId = $"{SearchContentProvider.ContentProviderName}/{_searchId++}_{baseContainer.Name}";
_baseContainer = baseContainer;
_timelessContentProvider = timelessContentProvider;
_matcher = matcher;
_container = new Container(
baseContainer.Name,
baseContainer.DisplayName,
new FullName(randomId),
new NativePath(randomId),
null,
parent,
false,
true,
null,
@@ -85,7 +89,11 @@ public class SearchTask : ISearchTask
foreach (var itemPath in items)
{
var item = await itemPath.ResolveAsync();
var item = await itemPath.ResolveAsync(
itemInitializationSettings: new ItemInitializationSettings
{
Parent = new AbsolutePath(_timelessContentProvider, _container)
});
if (await _matcher.IsItemMatchAsync(item))
{
_items.Add(itemPath);

View File

@@ -10,7 +10,8 @@ public static class Startup
{
services.TryAddSingleton<ISearchContentProvider, SearchContentProvider>();
services.AddSingleton<IContentProvider>(sp => sp.GetRequiredService<ISearchContentProvider>());
services.TryAddSingleton<ISearchManager, SearchManager>();
services.TryAddSingleton<IItemDeleter<ISearchContentProvider>, SearchItemDeleter>();
services.TryAddSingleton<IItemDeleter<SearchContentProvider>>(sp => sp.GetRequiredService<IItemDeleter<ISearchContentProvider>>());
return services;
}