New reactive core WIP
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Library\DeclarativeProperty\DeclarativeProperty.csproj" />
|
||||
<ProjectReference Include="..\..\Library\InitableService\InitableService.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@ namespace FileTime.Core.Models;
|
||||
|
||||
public interface IContainer : IItem
|
||||
{
|
||||
IObservable<IChangeSet<AbsolutePath, string>> Items { get; }
|
||||
ReadOnlyObservableCollection<AbsolutePath> ItemsCollection { get; }
|
||||
ObservableCollection<AbsolutePath> Items { get; }
|
||||
IObservable<bool> IsLoading { get; }
|
||||
bool? IsLoaded { get; }
|
||||
Task WaitForLoaded(CancellationToken token = default);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using DynamicData;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Enums;
|
||||
@@ -21,7 +22,7 @@ public interface IItem
|
||||
string? Attributes { get; }
|
||||
AbsolutePathType Type { get; }
|
||||
PointInTime PointInTime { get; }
|
||||
IObservable<IChangeSet<Exception>> Exceptions { get; }
|
||||
ObservableCollection<Exception> Exceptions { get; }
|
||||
ReadOnlyExtensionCollection Extensions { get; }
|
||||
|
||||
T? GetExtension<T>() => (T?)Extensions.FirstOrDefault(i => i is T);
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using DeclarativeProperty;
|
||||
using DynamicData;
|
||||
using FileTime.Core.Models;
|
||||
using InitableService;
|
||||
|
||||
namespace FileTime.Core.Services;
|
||||
|
||||
public interface ITab : IInitable<IContainer>, IDisposable
|
||||
public interface ITab : IAsyncInitable<IContainer>, IDisposable
|
||||
{
|
||||
IObservable<IContainer?> CurrentLocation { get; }
|
||||
IObservable<AbsolutePath?> CurrentSelectedItem { get; }
|
||||
IObservable<IObservable<IChangeSet<IItem, string>>?> CurrentItems { get; }
|
||||
public IDeclarativeProperty<IContainer?> CurrentLocation { get; }
|
||||
public IDeclarativeProperty<ObservableCollection<IItem>?> CurrentItems { get; }
|
||||
public IDeclarativeProperty<AbsolutePath?> CurrentSelectedItem { get; }
|
||||
FullName? LastDeepestSelectedPath { get; }
|
||||
|
||||
void SetCurrentLocation(IContainer newLocation);
|
||||
Task SetCurrentLocation(IContainer newLocation);
|
||||
void AddItemFilter(ItemFilter filter);
|
||||
void RemoveItemFilter(ItemFilter filter);
|
||||
void RemoveItemFilter(string name);
|
||||
void SetSelectedItem(AbsolutePath newSelectedItem);
|
||||
void ForceSetCurrentLocation(IContainer newLocation);
|
||||
Task SetSelectedItem(AbsolutePath newSelectedItem);
|
||||
Task ForceSetCurrentLocation(IContainer newLocation);
|
||||
}
|
||||
@@ -140,12 +140,12 @@ public class CopyCommand : CommandBase, ITransportationCommand
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
if (resolvedTarget.ItemsCollection.All(i => i.Path.GetName() != item.Name))
|
||||
if (resolvedTarget.Items.All(i => i.Path.GetName() != item.Name))
|
||||
{
|
||||
await copyOperation.CreateContainerAsync(resolvedTarget, container.Name, container.PointInTime);
|
||||
}
|
||||
|
||||
var children = container.ItemsCollection;
|
||||
var children = container.Items;
|
||||
|
||||
await TraverseTree(currentTime, children.Select(c => c.Path).ToList(), target.GetChild(item.Name, AbsolutePathType.Container), transportMode, copyOperation);
|
||||
await copyOperation.ContainerCopyDoneAsync(new AbsolutePath(_timelessContentProvider, container));
|
||||
|
||||
@@ -41,7 +41,7 @@ public abstract class CreateItemBase : CommandBase, IExecutableCommand, IInitabl
|
||||
var parent = await ResolveParentAsync();
|
||||
if (parent is not IContainer parentContainer) return CanCommandRun.False;
|
||||
|
||||
var items = parentContainer.ItemsCollection;
|
||||
var items = parentContainer.Items;
|
||||
var existingItem = items.FirstOrDefault(i => i.Path.GetName() == NewItemName);
|
||||
|
||||
return existingItem switch
|
||||
|
||||
@@ -73,7 +73,7 @@ public class DeleteCommand : CommandBase, IExecutableCommand
|
||||
{
|
||||
await TraverseTree(
|
||||
currentTime,
|
||||
container.ItemsCollection.Select(i => i.Path),
|
||||
container.Items.Select(i => i.Path),
|
||||
itemDeleters,
|
||||
deleteStrategy
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ public static class Helper
|
||||
public static async Task<string?> GetNewNameAsync(IContainer resolvedTarget, string name, TransportMode transportMode)
|
||||
{
|
||||
await resolvedTarget.WaitForLoaded();
|
||||
var items = resolvedTarget.ItemsCollection.ToList();
|
||||
var items = resolvedTarget.Items.ToList();
|
||||
var newName = name;
|
||||
var targetNameExists = items.Any(i => i.Path.GetName() == newName);
|
||||
if (transportMode == TransportMode.Merge)
|
||||
|
||||
@@ -52,7 +52,7 @@ public class StreamCopyCommandHandler : ICommandHandler
|
||||
{
|
||||
var parent = (IContainer?) (await targetPath.GetParent()!.ResolveAsync())!;
|
||||
var elementName = targetPath.Path;
|
||||
var parentChildren = parent.ItemsCollection.ToList();
|
||||
var parentChildren = parent.Items.ToList();
|
||||
if (parentChildren.All(e => e.Path.GetName() != elementName.GetName()))
|
||||
{
|
||||
var itemCreator = _contentAccessorFactory.GetItemCreator(parent.Provider);
|
||||
|
||||
@@ -11,14 +11,9 @@ namespace FileTime.Core.ContentAccess;
|
||||
public abstract class ContentProviderBase : IContentProvider
|
||||
{
|
||||
private readonly ReadOnlyExtensionCollection _extensions;
|
||||
private readonly IObservable<IChangeSet<AbsolutePath, string>> _items;
|
||||
|
||||
protected SourceCache<AbsolutePath, string> Items { get; } = new(p => p.Path.Path);
|
||||
public ReadOnlyObservableCollection<AbsolutePath> ItemsCollection { get; }
|
||||
public ObservableCollection<AbsolutePath> Items { get; }
|
||||
protected ExtensionCollection Extensions { get; }
|
||||
|
||||
IObservable<IChangeSet<AbsolutePath, string>> IContainer.Items => _items;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string DisplayName { get; }
|
||||
@@ -53,8 +48,7 @@ public abstract class ContentProviderBase : IContentProvider
|
||||
public AbsolutePathType Type => AbsolutePathType.Container;
|
||||
public PointInTime PointInTime { get; } = PointInTime.Eternal;
|
||||
|
||||
protected SourceList<Exception> Exceptions { get; } = new();
|
||||
IObservable<IChangeSet<Exception>> IItem.Exceptions => Exceptions.Connect();
|
||||
public ObservableCollection<Exception> Exceptions { get; } = new();
|
||||
|
||||
ReadOnlyExtensionCollection IItem.Extensions => _extensions;
|
||||
|
||||
@@ -64,9 +58,8 @@ public abstract class ContentProviderBase : IContentProvider
|
||||
FullName = FullName.CreateSafe(name);
|
||||
Extensions = new ExtensionCollection();
|
||||
_extensions = Extensions.AsReadOnly();
|
||||
_items = Items.Connect().StartWithEmpty();
|
||||
_items.Bind(out var items).Subscribe();
|
||||
ItemsCollection = items;
|
||||
//TODO:
|
||||
Items = new ObservableCollection<AbsolutePath>();
|
||||
}
|
||||
|
||||
public virtual Task OnEnter() => Task.CompletedTask;
|
||||
|
||||
@@ -23,17 +23,10 @@ public record Container(
|
||||
IContentProvider Provider,
|
||||
bool AllowRecursiveDeletion,
|
||||
PointInTime PointInTime,
|
||||
IObservable<IChangeSet<Exception>> Exceptions,
|
||||
ObservableCollection<Exception> Exceptions,
|
||||
ReadOnlyExtensionCollection Extensions,
|
||||
IObservable<IChangeSet<AbsolutePath, string>> Items) : IContainer
|
||||
ObservableCollection<AbsolutePath> Items) : IContainer
|
||||
{
|
||||
|
||||
private readonly Lazy<ReadOnlyObservableCollection<AbsolutePath>> _itemsCollectionLazy =
|
||||
new(() =>
|
||||
{
|
||||
Items.Bind(out var items).Subscribe();
|
||||
return items;
|
||||
});
|
||||
private readonly CancellationTokenSource _loadingCancellationTokenSource = new();
|
||||
private readonly BehaviorSubject<bool> _isLoading = new(false);
|
||||
|
||||
@@ -42,8 +35,6 @@ public record Container(
|
||||
public bool? IsLoaded { get; private set; }
|
||||
public AbsolutePathType Type => AbsolutePathType.Container;
|
||||
|
||||
public ReadOnlyObservableCollection<AbsolutePath> ItemsCollection => _itemsCollectionLazy.Value;
|
||||
|
||||
public async Task WaitForLoaded(CancellationToken token = default)
|
||||
{
|
||||
while (IsLoaded != true) await Task.Delay(1, token);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using DynamicData;
|
||||
using FileTime.Core.ContentAccess;
|
||||
using FileTime.Core.Enums;
|
||||
@@ -19,7 +20,7 @@ public record Element(
|
||||
string? Attributes,
|
||||
IContentProvider Provider,
|
||||
PointInTime PointInTime,
|
||||
IObservable<IChangeSet<Exception>> Exceptions,
|
||||
ObservableCollection<Exception> Exceptions,
|
||||
ReadOnlyExtensionCollection Extensions) : IElement
|
||||
{
|
||||
public AbsolutePathType Type => AbsolutePathType.Element;
|
||||
|
||||
@@ -11,12 +11,15 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ObservableComputations" Version="2.3.0" />
|
||||
<PackageReference Include="System.Interactive.Async" Version="6.0.1" />
|
||||
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
|
||||
<PackageReference Include="System.Reactive" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
|
||||
<ProjectReference Include="..\..\Library\Defer\Defer.csproj" />
|
||||
<ProjectReference Include="..\..\Tools\FileTime.Tools\FileTime.Tools.csproj" />
|
||||
<ProjectReference Include="..\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
|
||||
<ProjectReference Include="..\FileTime.Core.Models\FileTime.Core.Models.csproj" />
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
using System.Reactive.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Subjects;
|
||||
using DeclarativeProperty;
|
||||
using DynamicData;
|
||||
using DynamicData.Alias;
|
||||
using DynamicData.Binding;
|
||||
using FileTime.App.Core.Services;
|
||||
using FileTime.Core.Helper;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
using ObservableComputations;
|
||||
using static System.DeferTools;
|
||||
|
||||
namespace FileTime.Core.Services;
|
||||
|
||||
@@ -13,27 +18,46 @@ public class Tab : ITab
|
||||
{
|
||||
private readonly ITimelessContentProvider _timelessContentProvider;
|
||||
private readonly ITabEvents _tabEvents;
|
||||
private readonly BehaviorSubject<IContainer?> _currentLocation = new(null);
|
||||
private readonly IRefreshSmoothnessCalculator _refreshSmoothnessCalculator;
|
||||
private readonly DeclarativeProperty<IContainer?> _currentLocation = new(null);
|
||||
private readonly BehaviorSubject<IContainer?> _currentLocationForced = new(null);
|
||||
private readonly BehaviorSubject<AbsolutePath?> _currentSelectedItem = new(null);
|
||||
private readonly DeclarativeProperty<AbsolutePath?> _currentRequestItem = new(null);
|
||||
private readonly SourceList<ItemFilter> _itemFilters = new();
|
||||
private AbsolutePath? _currentSelectedItemCached;
|
||||
private PointInTime _currentPointInTime;
|
||||
private OcConsumer? _currentItemsConsumer;
|
||||
private CancellationTokenSource? _setCurrentLocationCancellationTokenSource;
|
||||
private CancellationTokenSource? _setCurrentItemCancellationTokenSource;
|
||||
|
||||
public IObservable<IContainer?> CurrentLocation { get; }
|
||||
public IObservable<IObservable<IChangeSet<IItem, string>>?> CurrentItems { get; }
|
||||
public IObservable<AbsolutePath?> CurrentSelectedItem { get; }
|
||||
public IDeclarativeProperty<IContainer?> CurrentLocation { get; }
|
||||
public IDeclarativeProperty<ObservableCollection<IItem>?> CurrentItems { get; }
|
||||
public IDeclarativeProperty<AbsolutePath?> CurrentSelectedItem { get; }
|
||||
public FullName? LastDeepestSelectedPath { get; private set; }
|
||||
|
||||
public Tab(ITimelessContentProvider timelessContentProvider, ITabEvents tabEvents)
|
||||
public Tab(
|
||||
ITimelessContentProvider timelessContentProvider,
|
||||
ITabEvents tabEvents,
|
||||
IRefreshSmoothnessCalculator refreshSmoothnessCalculator)
|
||||
{
|
||||
_timelessContentProvider = timelessContentProvider;
|
||||
_tabEvents = tabEvents;
|
||||
_refreshSmoothnessCalculator = refreshSmoothnessCalculator;
|
||||
_currentPointInTime = null!;
|
||||
|
||||
_timelessContentProvider.CurrentPointInTime.Subscribe(p => _currentPointInTime = p);
|
||||
|
||||
CurrentLocation = _currentLocation
|
||||
CurrentLocation = _currentLocation;
|
||||
CurrentLocation.Subscribe((c, _) =>
|
||||
{
|
||||
if (_currentSelectedItemCached is not null)
|
||||
{
|
||||
LastDeepestSelectedPath = FullName.CreateSafe(PathHelper.GetLongerPath(LastDeepestSelectedPath?.Path, _currentSelectedItemCached.Path.Path));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
/*CurrentLocation = _currentLocation
|
||||
.DistinctUntilChanged()
|
||||
.Merge(_currentLocationForced)
|
||||
.Do(_ =>
|
||||
@@ -44,14 +68,43 @@ public class Tab : ITab
|
||||
}
|
||||
})
|
||||
.Publish(null)
|
||||
.RefCount();
|
||||
.RefCount();*/
|
||||
|
||||
CurrentItems =
|
||||
CurrentItems = CurrentLocation.Map((container, _) =>
|
||||
{
|
||||
var items = container is null
|
||||
? (ObservableCollection<IItem>?) null
|
||||
: container.Items.Selecting<AbsolutePath, IItem>(i => MapItem(i));
|
||||
return Task.FromResult(items);
|
||||
}
|
||||
) /*.Watch<ObservableCollection<IItem>, IItem>()*/;
|
||||
/*using var _ = Defer(() =>
|
||||
CurrentItems.Subscribe(c => UpdateConsumer(c, ref _currentItemsConsumer))
|
||||
);*/
|
||||
|
||||
/*CurrentItems.RegisterTrigger(
|
||||
(sender, items) =>
|
||||
{
|
||||
if (items is null)
|
||||
return null;
|
||||
|
||||
items.CollectionChanged += Handler;
|
||||
|
||||
return Disposable.Create(() => items.CollectionChanged -= Handler);
|
||||
|
||||
void Handler(object? o, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
var t = Task.Run(async () => await sender.ReFireAsync());
|
||||
t.Wait();
|
||||
}
|
||||
});*/
|
||||
|
||||
/*CurrentItems =
|
||||
Observable.Merge(
|
||||
Observable.CombineLatest(
|
||||
CurrentLocation
|
||||
.Where(c => c is not null)
|
||||
.Select(c => c!.Items)
|
||||
.Select(c => c!.ItemsCollection)
|
||||
.Select(items => items.TransformAsync(MapItem)),
|
||||
_itemFilters.Connect().StartWithEmpty().ToCollection(),
|
||||
(items, filters) =>
|
||||
@@ -66,9 +119,26 @@ public class Tab : ITab
|
||||
.Select(_ => (IObservable<IChangeSet<IItem, string>>?) null)
|
||||
)
|
||||
.Publish(null)
|
||||
.RefCount();
|
||||
.RefCount();*/
|
||||
CurrentSelectedItem = DeclarativePropertyHelpers.CombineLatest(
|
||||
CurrentItems.Watch<ObservableCollection<IItem>, IItem>(),
|
||||
_currentRequestItem.DistinctUntilChanged(),
|
||||
(items, selected) =>
|
||||
{
|
||||
if (selected != null && (items?.Any(i => i.FullName == selected.Path) ?? true)) return Task.FromResult<AbsolutePath?>(selected);
|
||||
if (items == null || items.Count == 0) return Task.FromResult<AbsolutePath?>(null);
|
||||
|
||||
CurrentSelectedItem =
|
||||
return Task.FromResult(GetSelectedItemByItems(items));
|
||||
}).DistinctUntilChanged();
|
||||
|
||||
CurrentSelectedItem.Subscribe((v) =>
|
||||
{
|
||||
_refreshSmoothnessCalculator.RegisterChange();
|
||||
_refreshSmoothnessCalculator.RecalculateSmoothness();
|
||||
});
|
||||
|
||||
|
||||
/*CurrentSelectedItem =
|
||||
Observable.CombineLatest(
|
||||
CurrentItems
|
||||
.Select(c =>
|
||||
@@ -88,27 +158,40 @@ public class Tab : ITab
|
||||
)
|
||||
.DistinctUntilChanged()
|
||||
.Publish(null)
|
||||
.RefCount();
|
||||
.RefCount();*/
|
||||
|
||||
CurrentSelectedItem.Subscribe(s =>
|
||||
CurrentSelectedItem.Subscribe(async (s, _) =>
|
||||
{
|
||||
_currentSelectedItemCached = s;
|
||||
_currentSelectedItem.OnNext(s);
|
||||
await _currentRequestItem.SetValue(s);
|
||||
});
|
||||
}
|
||||
|
||||
static void UpdateConsumer<T>(ObservableCollection<T>? collection, ref OcConsumer? consumer)
|
||||
{
|
||||
if (collection is not IComputing computing) return;
|
||||
|
||||
consumer?.Dispose();
|
||||
consumer = new OcConsumer();
|
||||
computing.For(consumer);
|
||||
}
|
||||
|
||||
private static IItem MapItem(AbsolutePath item)
|
||||
{
|
||||
var t = Task.Run(async () => await item.ResolveAsync(true));
|
||||
t.Wait();
|
||||
return t.Result;
|
||||
}
|
||||
|
||||
private static SortExpressionComparer<IItem> SortItems()
|
||||
//TODO: Order
|
||||
=> SortExpressionComparer<IItem>
|
||||
.Ascending(i => i.Type)
|
||||
.ThenByAscending(i => i.DisplayName.ToLower());
|
||||
|
||||
private async Task<IItem> MapItem(AbsolutePath item) => await item.ResolveAsync(true);
|
||||
|
||||
public void Init(IContainer currentLocation)
|
||||
{
|
||||
_currentLocation.OnNext(currentLocation);
|
||||
}
|
||||
public async Task InitAsync(IContainer currentLocation)
|
||||
=> await _currentLocation.SetValue(currentLocation);
|
||||
|
||||
private AbsolutePath? GetSelectedItemByItems(IReadOnlyCollection<IItem> items)
|
||||
{
|
||||
@@ -140,9 +223,11 @@ public class Tab : ITab
|
||||
return newSelectedItem;
|
||||
}
|
||||
|
||||
public void SetCurrentLocation(IContainer newLocation)
|
||||
public async Task SetCurrentLocation(IContainer newLocation)
|
||||
{
|
||||
_currentLocation.OnNext(newLocation);
|
||||
_setCurrentLocationCancellationTokenSource?.Cancel();
|
||||
_setCurrentLocationCancellationTokenSource = new CancellationTokenSource();
|
||||
await _currentLocation.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token);
|
||||
|
||||
if (newLocation.FullName != null)
|
||||
{
|
||||
@@ -150,9 +235,11 @@ public class Tab : ITab
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceSetCurrentLocation(IContainer newLocation)
|
||||
public async Task ForceSetCurrentLocation(IContainer newLocation)
|
||||
{
|
||||
_currentLocationForced.OnNext(newLocation);
|
||||
_setCurrentLocationCancellationTokenSource?.Cancel();
|
||||
_setCurrentLocationCancellationTokenSource = new CancellationTokenSource();
|
||||
await _currentLocation.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token);
|
||||
|
||||
if (newLocation.FullName != null)
|
||||
{
|
||||
@@ -160,7 +247,12 @@ public class Tab : ITab
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSelectedItem(AbsolutePath newSelectedItem) => _currentSelectedItem.OnNext(newSelectedItem);
|
||||
public async Task SetSelectedItem(AbsolutePath newSelectedItem)
|
||||
{
|
||||
_setCurrentItemCancellationTokenSource?.Cancel();
|
||||
_setCurrentItemCancellationTokenSource = new CancellationTokenSource();
|
||||
await _currentRequestItem.SetValue(newSelectedItem, _setCurrentItemCancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
public void AddItemFilter(ItemFilter filter) => _itemFilters.Add(filter);
|
||||
public void RemoveItemFilter(ItemFilter filter) => _itemFilters.Remove(filter);
|
||||
@@ -178,13 +270,12 @@ public class Tab : ITab
|
||||
await _currentSelectedItemCached.TimelessProvider.GetItemByFullNameAsync(_currentSelectedItemCached.Path, _currentPointInTime);
|
||||
|
||||
if (resolvedSelectedItem is not IContainer resolvedContainer) return;
|
||||
SetCurrentLocation(resolvedContainer);
|
||||
await SetCurrentLocation(resolvedContainer);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_currentLocation.Dispose();
|
||||
_currentSelectedItem.Dispose();
|
||||
_itemFilters.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user