Use SourceCache, set SelectedItem and scroll to it

This commit is contained in:
2022-05-25 08:52:07 +02:00
parent d94d198344
commit 06f79ddfa9
14 changed files with 112 additions and 53 deletions

View File

@@ -47,6 +47,56 @@ public partial class BindedCollection<T> : IDisposable, INotifyPropertyChanged
});
}
public void Dispose()
{
_disposable?.Dispose();
_innerDisposable?.Dispose();
GC.SuppressFinalize(this);
}
}
public partial class BindedCollection<T, TKey> : IDisposable, INotifyPropertyChanged where TKey : notnull
{
private readonly IDisposable? _disposable;
private IDisposable? _innerDisposable;
[Notify] private ReadOnlyObservableCollection<T>? _collection;
public BindedCollection()
{
}
public BindedCollection(IObservable<IChangeSet<T, TKey>> dynamicList)
{
_disposable = dynamicList
.Bind(out var collection)
.DisposeMany()
.Subscribe();
_collection = collection;
}
public BindedCollection(IObservable<IObservable<IChangeSet<T, TKey>>?> dynamicListSource)
{
_disposable = dynamicListSource.Subscribe(dynamicList =>
{
_innerDisposable?.Dispose();
if (dynamicList is not null)
{
_innerDisposable = dynamicList
.Bind(out var collection)
.DisposeMany()
.Subscribe();
Collection = collection;
}
else
{
Collection = null;
}
});
}
public void Dispose()
{
_disposable?.Dispose();

View File

@@ -14,13 +14,13 @@ public interface ITabViewModel : IInitable<ITab, int>, IDisposable
IObservable<bool> IsSelected { get; }
IObservable<IContainer?> CurrentLocation { get; }
IObservable<IItemViewModel?> CurrentSelectedItem { get; }
IObservable<IObservable<IChangeSet<IItemViewModel>>?> CurrentItems { get; }
IObservable<IObservable<IChangeSet<IItemViewModel, string>>?> CurrentItems { get; }
IObservable<IChangeSet<FullName>> MarkedItems { get; }
IObservable<IObservable<IChangeSet<IItemViewModel>>?> SelectedsChildren { get; }
IObservable<IObservable<IChangeSet<IItemViewModel>>?> ParentsChildren { get; }
BindedCollection<IItemViewModel>? CurrentItemsCollection { get; }
BindedCollection<IItemViewModel>? SelectedsChildrenCollection { get; }
BindedCollection<IItemViewModel>? ParentsChildrenCollection { get; }
IObservable<IObservable<IChangeSet<IItemViewModel, string>>?> SelectedsChildren { get; }
IObservable<IObservable<IChangeSet<IItemViewModel, string>>?> ParentsChildren { get; }
BindedCollection<IItemViewModel, string>? CurrentItemsCollection { get; }
BindedCollection<IItemViewModel, string>? SelectedsChildrenCollection { get; }
BindedCollection<IItemViewModel, string>? ParentsChildrenCollection { get; }
IObservable<IReadOnlyCollection<IItemViewModel>?> CurrentItemsCollectionObservable { get; }
IObservable<IReadOnlyCollection<IItemViewModel>?> ParentsChildrenCollectionObservable { get; }
IObservable<IReadOnlyCollection<IItemViewModel>?> SelectedsChildrenCollectionObservable { get; }

View File

@@ -31,10 +31,10 @@ public partial class TabViewModel : ITabViewModel
public IObservable<IContainer?> CurrentLocation { get; private set; } = null!;
public IObservable<IItemViewModel?> CurrentSelectedItem { get; private set; } = null!;
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> CurrentItems { get; private set; } = null!;
public IObservable<IObservable<IChangeSet<IItemViewModel, string>>?> CurrentItems { get; private set; } = null!;
public IObservable<IChangeSet<FullName>> MarkedItems { get; }
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> SelectedsChildren { get; private set; } = null!;
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> ParentsChildren { get; private set; } = null!;
public IObservable<IObservable<IChangeSet<IItemViewModel, string>>?> SelectedsChildren { get; private set; } = null!;
public IObservable<IObservable<IChangeSet<IItemViewModel, string>>?> ParentsChildren { get; private set; } = null!;
public IObservable<IReadOnlyCollection<IItemViewModel>?> CurrentItemsCollectionObservable { get; private set; } =
null!;
@@ -46,11 +46,11 @@ public partial class TabViewModel : ITabViewModel
SelectedsChildrenCollectionObservable
{ get; private set; } = null!;
[Property] private BindedCollection<IItemViewModel>? _currentItemsCollection;
[Property] private BindedCollection<IItemViewModel, string>? _currentItemsCollection;
[Property] private BindedCollection<IItemViewModel>? _parentsChildrenCollection;
[Property] private BindedCollection<IItemViewModel, string>? _parentsChildrenCollection;
[Property] private BindedCollection<IItemViewModel>? _selectedsChildrenCollection;
[Property] private BindedCollection<IItemViewModel, string>? _selectedsChildrenCollection;
public IContainer? CachedCurrentLocation { get; private set; }
@@ -81,12 +81,10 @@ public partial class TabViewModel : ITabViewModel
CurrentItems = tab.CurrentItems
.Select(items => items?.Transform(i => MapItemToViewModel(i, ItemViewModelType.Main)))
/*.ObserveOn(_rxSchedulerService.GetWorkerScheduler())
.SubscribeOn(_rxSchedulerService.GetUIScheduler())*/
.Publish(null)
.RefCount();
CurrentSelectedItem =
/*CurrentSelectedItem =
Observable.CombineLatest(
CurrentItems,
tab.CurrentSelectedItem,
@@ -96,18 +94,28 @@ public partial class TabViewModel : ITabViewModel
: currentItems
.ToCollection()
.Select(items =>
items.FirstOrDefault(i => i.BaseItem?.FullName == currentSelectedItemPath?.Path))
items.FirstOrDefault(i => i.BaseItem?.FullName?.Path == currentSelectedItemPath?.Path.Path))
)
.Switch()
.Publish(null)
.RefCount();*/
CurrentSelectedItem =
Observable.CombineLatest(
CurrentItems,
tab.CurrentSelectedItem,
(currentItems, currentSelectedItemPath) =>
CurrentItemsCollection?.Collection?.FirstOrDefault(i => i.BaseItem?.FullName?.Path == currentSelectedItemPath?.Path.Path)
)
.Publish(null)
.RefCount();
SelectedsChildren = InitSelectedsChildren();
ParentsChildren = InitParentsChildren();
CurrentItemsCollectionObservable = InitAsd(CurrentItems);
SelectedsChildrenCollectionObservable = InitAsd(SelectedsChildren);
ParentsChildrenCollectionObservable = InitAsd(ParentsChildren);
CurrentItemsCollectionObservable = InitCollection(CurrentItems);
SelectedsChildrenCollectionObservable = InitCollection(SelectedsChildren);
ParentsChildrenCollectionObservable = InitCollection(ParentsChildren);
CurrentItemsCollection = new(CurrentItems);
ParentsChildrenCollection = new(ParentsChildren);
@@ -115,7 +123,7 @@ public partial class TabViewModel : ITabViewModel
tab.CurrentLocation.Subscribe((_) => _markedItems.Clear()).AddToDisposables(_disposables);
IObservable<IObservable<IChangeSet<IItemViewModel>>?> InitSelectedsChildren()
IObservable<IObservable<IChangeSet<IItemViewModel, string>>?> InitSelectedsChildren()
{
var currentSelectedItemThrottled =
CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250)).Publish(null).RefCount();
@@ -131,7 +139,7 @@ public partial class TabViewModel : ITabViewModel
.Transform(i => MapItemToViewModel(i, ItemViewModelType.SelectedChild))),
currentSelectedItemThrottled
.Where(c => c is null or not IContainerViewModel)
.Select(_ => (IObservable<IChangeSet<IItemViewModel>>?)null)
.Select(_ => (IObservable<IChangeSet<IItemViewModel, string>>?)null)
)
/*.ObserveOn(_rxSchedulerService.GetWorkerScheduler())
.SubscribeOn(_rxSchedulerService.GetUIScheduler())*/
@@ -139,7 +147,7 @@ public partial class TabViewModel : ITabViewModel
.RefCount();
}
IObservable<IObservable<IChangeSet<IItemViewModel>>?> InitParentsChildren()
IObservable<IObservable<IChangeSet<IItemViewModel, string>>?> InitParentsChildren()
{
var parentThrottled = CurrentLocation
.Select(l => l?.Parent)
@@ -159,7 +167,7 @@ public partial class TabViewModel : ITabViewModel
.Transform(i => MapItemToViewModel(i, ItemViewModelType.Parent))),
parentThrottled
.Where(p => p is null)
.Select(_ => (IObservable<IChangeSet<IItemViewModel>>?)null)
.Select(_ => (IObservable<IChangeSet<IItemViewModel, string>>?)null)
)
/*.ObserveOn(_rxSchedulerService.GetWorkerScheduler())
.SubscribeOn(_rxSchedulerService.GetUIScheduler())*/
@@ -167,8 +175,8 @@ public partial class TabViewModel : ITabViewModel
.RefCount();
}
IObservable<IReadOnlyCollection<IItemViewModel>?> InitAsd(
IObservable<IObservable<IChangeSet<IItemViewModel>>?> source)
IObservable<IReadOnlyCollection<IItemViewModel>?> InitCollection(
IObservable<IObservable<IChangeSet<IItemViewModel, string>>?> source)
{
return source
.Select(c =>

View File

@@ -41,7 +41,7 @@ public static class DynamicDataExtensions
}
public static async Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
this IObservable<IObservable<IChangeSet<AbsolutePath>>?> stream)
this IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> stream)
=> await GetItemsAsync(stream
.Select(s =>
s is null
@@ -50,7 +50,7 @@ public static class DynamicDataExtensions
.Switch());
public static async Task<IEnumerable<AbsolutePath>?> GetItemsAsync(
this IObservable<IChangeSet<AbsolutePath>> stream)
this IObservable<IChangeSet<AbsolutePath, string>> stream)
=> await GetItemsAsync(stream.ToCollection());
private static Task<IEnumerable<AbsolutePath>?> GetItemsAsync(

View File

@@ -4,6 +4,6 @@ namespace FileTime.Core.Models;
public interface IContainer : IItem
{
IObservable<IObservable<IChangeSet<AbsolutePath>>?> Items { get; }
IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> Items { get; }
IObservable<bool> IsLoading { get; }
}

View File

@@ -8,7 +8,7 @@ public interface ITab : IInitable<IContainer>, IDisposable
{
IObservable<IContainer?> CurrentLocation { get; }
IObservable<AbsolutePath?> CurrentSelectedItem { get; }
IObservable<IObservable<IChangeSet<IItem>>?> CurrentItems { get; }
IObservable<IObservable<IChangeSet<IItem, string>>?> CurrentItems { get; }
FullName? LastDeepestSelectedPath { get; }
void SetCurrentLocation(IContainer newLocation);

View File

@@ -11,10 +11,10 @@ public abstract class ContentProviderBase : IContentProvider
{
private readonly ReadOnlyExtensionCollection _extensions;
protected BehaviorSubject<IObservable<IChangeSet<AbsolutePath>>?> Items { get; } = new(null);
protected BehaviorSubject<IObservable<IChangeSet<AbsolutePath, string>>?> Items { get; } = new(null);
protected ExtensionCollection Extensions { get; }
IObservable<IObservable<IChangeSet<AbsolutePath>>?> IContainer.Items => Items;
IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> IContainer.Items => Items;
public string Name { get; }

View File

@@ -25,7 +25,7 @@ public record Container(
PointInTime PointInTime,
IObservable<IEnumerable<Exception>> Exceptions,
ReadOnlyExtensionCollection Extensions,
IObservable<IObservable<IChangeSet<AbsolutePath>>?> Items) : IContainer
IObservable<IObservable<IChangeSet<AbsolutePath, string>>?> Items) : IContainer
{
BehaviorSubject<bool> IsLoading { get; } = new BehaviorSubject<bool>(false);
IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable();

View File

@@ -19,7 +19,7 @@ public class Tab : ITab
private PointInTime _currentPointInTime;
public IObservable<IContainer?> CurrentLocation { get; }
public IObservable<IObservable<IChangeSet<IItem>>?> CurrentItems { get; }
public IObservable<IObservable<IChangeSet<IItem, string>>?> CurrentItems { get; }
public IObservable<AbsolutePath?> CurrentSelectedItem { get; }
public FullName? LastDeepestSelectedPath { get; private set; }
@@ -55,9 +55,9 @@ public class Tab : ITab
(items, filters) => items?.Where(i => filters.All(f => f.Filter(i)))),
CurrentLocation
.Where(c => c is null)
.Select(_ => (IObservable<IChangeSet<IItem>>?)null)
.Select(_ => (IObservable<IChangeSet<IItem, string>>?)null)
)
.Publish((IObservable<IChangeSet<IItem>>?)null)
.Publish((IObservable<IChangeSet<IItem, string>>?)null)
.RefCount();
CurrentSelectedItem =

View File

@@ -11,5 +11,5 @@ public interface IGuiAppState : IAppState
bool NoCommandFound { get; set; }
string? MessageBoxText { get; set; }
List<CommandBindingConfiguration> PossibleCommands { get; set; }
BindedCollection<RootDriveInfo> RootDriveInfos { get; set; }
BindedCollection<RootDriveInfo, string> RootDriveInfos { get; set; }
}

View File

@@ -17,7 +17,7 @@ public partial class GuiAppState : AppStateBase, IGuiAppState
[Property] private List<CommandBindingConfiguration> _possibleCommands = new();
[Property] private BindedCollection<RootDriveInfo> _rootDriveInfos = new();
[Property] private BindedCollection<RootDriveInfo, string> _rootDriveInfos = new();
public List<KeyConfig> PreviousKeys { get; } = new();
}

View File

@@ -15,14 +15,15 @@ namespace FileTime.GuiApp.Services;
public class RootDriveInfoService : IStartupHandler
{
private readonly SourceList<DriveInfo> _rootDrives = new();
private readonly IObservable<IChangeSet<AbsolutePath>> _localContentProviderStream;
private readonly IObservable<IChangeSet<AbsolutePath, string>> _localContentProviderStream;
public RootDriveInfoService(IGuiAppState guiAppState, ILocalContentProvider localContentProvider, ITimelessContentProvider timelessContentProvider)
public RootDriveInfoService(IGuiAppState guiAppState, ILocalContentProvider localContentProvider,
ITimelessContentProvider timelessContentProvider)
{
InitRootDrives();
var localContentProviderAsList = new SourceList<AbsolutePath>();
localContentProviderAsList.Add(new AbsolutePath(timelessContentProvider, localContentProvider));
var localContentProviderAsList = new SourceCache<AbsolutePath, string>(i => i.Path.Path);
localContentProviderAsList.AddOrUpdate(new AbsolutePath(timelessContentProvider, localContentProvider));
_localContentProviderStream = localContentProviderAsList.Connect();
var rootDriveInfos = Observable.CombineLatest(
@@ -31,7 +32,7 @@ public class RootDriveInfoService : IStartupHandler
(items, drives) =>
{
return items is null
? Observable.Empty<IChangeSet<(AbsolutePath Path, DriveInfo? Drive)>>()
? Observable.Empty<IChangeSet<(AbsolutePath Path, DriveInfo? Drive), string>>()
: items!
.Or(new[] { _localContentProviderStream })
.Transform(i => (Path: i, Drive: drives.FirstOrDefault(d =>
@@ -39,7 +40,8 @@ public class RootDriveInfoService : IStartupHandler
var containerPath = localContentProvider.GetNativePath(i.Path).Path;
var drivePath = d.Name.TrimEnd(Path.DirectorySeparatorChar);
return containerPath == drivePath
|| (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" && d.Name == "/");
|| (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" &&
d.Name == "/");
})))
.Filter(t => t.Drive is not null);
}
@@ -51,7 +53,7 @@ public class RootDriveInfoService : IStartupHandler
.Transform(t => new RootDriveInfo(t.Drive, t.Container))
.Sort(SortExpressionComparer<RootDriveInfo>.Ascending(d => d.Name));
guiAppState.RootDriveInfos = new BindedCollection<RootDriveInfo>(rootDriveInfos);
guiAppState.RootDriveInfos = new BindedCollection<RootDriveInfo, string>(rootDriveInfos);
void InitRootDrives()
{

View File

@@ -327,11 +327,11 @@
<ListBox
x:Name="CurrentItems"
Grid.Row="1"
x:CompileBindings="False"
AutoScrollToSelectedItem="True"
Classes="ContentListView"
IsTabStop="True"
Items="{Binding AppState.SelectedTab^.CurrentItemsCollection.Collection}"
SelectedItem="{Binding AppState.SelectedTab^.CurrentSelectedItem^}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.ItemTemplate>

View File

@@ -6,7 +6,6 @@ using FileTime.App.Core.Models;
using FileTime.Core.ContentAccess;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Services;
using FileTime.Core.Timeline;
namespace FileTime.Providers.Local;
@@ -14,7 +13,7 @@ namespace FileTime.Providers.Local;
public sealed partial class LocalContentProvider : ContentProviderBase, ILocalContentProvider
{
private readonly ITimelessContentProvider _timelessContentProvider;
private readonly SourceList<AbsolutePath> _rootDirectories = new();
private readonly SourceCache<AbsolutePath, string> _rootDirectories = new(i => i.Path.Path);
private readonly bool _isCaseInsensitive;
public LocalContentProvider(ITimelessContentProvider timelessContentProvider) : base("local")
@@ -43,7 +42,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
_rootDirectories.Edit(actions =>
{
actions.Clear();
actions.AddRange(rootDirectories.Select(d => DirectoryToAbsolutePath(d, PointInTime.Present)));
actions.AddOrUpdate(rootDirectories.Select(d => DirectoryToAbsolutePath(d, PointInTime.Present)));
});
}
@@ -147,7 +146,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
pointInTime,
nonNullExceptions,
new ExtensionCollection().AsReadOnly(),
Observable.Return<IObservable<IChangeSet<AbsolutePath>>?>(null)
Observable.Return<IObservable<IChangeSet<AbsolutePath, string>>?>(null)
);
}
@@ -216,16 +215,16 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
Observable.FromAsync(async () => await Task.Run(InitChildren))
);
Task<IObservable<IChangeSet<AbsolutePath>>?> InitChildren()
Task<IObservable<IChangeSet<AbsolutePath, string>>?> InitChildren()
{
SourceList<AbsolutePath>? result = null;
SourceCache<AbsolutePath, string>? result = null;
try
{
var items = initializeChildren ? (List<AbsolutePath>?)GetItemsByContainer(directoryInfo, pointInTime) : null;
if (items != null)
{
result = new SourceList<AbsolutePath>();
result.AddRange(items);
result = new SourceCache<AbsolutePath, string>(i => i.Path.Path);
result.AddOrUpdate(items);
}
}
catch (Exception e)