using System.Collections.ObjectModel; using CircularBuffer; using DeclarativeProperty; using DynamicData; using FileTime.App.Core.Services; using FileTime.Core.Helper; using FileTime.Core.Models; using FileTime.Core.Timeline; using ObservableComputations; namespace FileTime.Core.Services; public class Tab : ITab { private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITabEvents _tabEvents; private readonly DeclarativeProperty _currentLocation = new(null); private readonly DeclarativeProperty _currentLocationForced = new(null); private readonly DeclarativeProperty _currentRequestItem = new(null); private readonly ObservableCollection _itemFilters = new(); private readonly CircularBuffer _history = new(20); private readonly CircularBuffer _future = new(20); private AbsolutePath? _currentSelectedItemCached; private PointInTime _currentPointInTime; private CancellationTokenSource? _setCurrentLocationCancellationTokenSource; public IDeclarativeProperty CurrentLocation { get; } public IDeclarativeProperty?> CurrentItems { get; } public IDeclarativeProperty CurrentSelectedItem { get; } public FullName? LastDeepestSelectedPath { get; private set; } public Tab( ITimelessContentProvider timelessContentProvider, ITabEvents tabEvents, IRefreshSmoothnessCalculator refreshSmoothnessCalculator) { _timelessContentProvider = timelessContentProvider; _tabEvents = tabEvents; _currentPointInTime = null!; var itemFiltersProperty = new DeclarativeProperty>(_itemFilters) .Watch, ItemFilter>(); _timelessContentProvider.CurrentPointInTime.Subscribe(p => _currentPointInTime = p); CurrentLocation = DeclarativePropertyHelpers.Merge( _currentLocation.DistinctUntilChanged(), _currentLocationForced ); CurrentLocation.Subscribe((c, _) => { if (_currentSelectedItemCached is not null) { LastDeepestSelectedPath = FullName.CreateSafe(PathHelper.GetLongerPath(LastDeepestSelectedPath?.Path, _currentSelectedItemCached.Path.Path)); } return Task.CompletedTask; }); CurrentItems = DeclarativePropertyHelpers.CombineLatest( CurrentLocation, itemFiltersProperty, (container, filters) => { ObservableCollection? items = null; if (container is not null) { items = container .Items .Selecting(i => MapItem(i)); if (filters is not null) { items = items.Filtering(i => filters.All(f => f.Filter(i))); } } return Task.FromResult(items); } ); CurrentSelectedItem = DeclarativePropertyHelpers.CombineLatest( CurrentItems.Watch, IItem>(), _currentRequestItem.DistinctUntilChanged(), (items, selected) => { if (selected != null && (items?.Any(i => i.FullName == selected.Path) ?? true)) return Task.FromResult(selected); if (items == null || items.Count == 0) return Task.FromResult(null); return Task.FromResult(GetSelectedItemByItems(items)); }).DistinctUntilChanged(); CurrentSelectedItem.Subscribe((v) => { refreshSmoothnessCalculator.RegisterChange(); refreshSmoothnessCalculator.RecalculateSmoothness(); }); CurrentSelectedItem.Subscribe(async (s, _) => { _currentSelectedItemCached = s; await _currentRequestItem.SetValue(s); }); } private static IItem MapItem(AbsolutePath item) { var t = Task.Run(async () => await item.ResolveAsync(true)); t.Wait(); return t.Result; } public async Task InitAsync(IContainer currentLocation) => await SetCurrentLocation(currentLocation); private AbsolutePath? GetSelectedItemByItems(IReadOnlyCollection items) { if (!items.Any()) return null; var newSelectedItem = new AbsolutePath(_timelessContentProvider, items.First()); if (LastDeepestSelectedPath is not null) { var parentPath = items.First().FullName?.GetParent()?.Path; if (parentPath is not null && LastDeepestSelectedPath.Path.StartsWith(parentPath)) { var itemNameToSelect = LastDeepestSelectedPath.Path .Split(Constants.SeparatorChar) .Skip(parentPath == "" ? 0 : parentPath.Split(Constants.SeparatorChar).Length) .FirstOrDefault(); var itemToSelect = items.FirstOrDefault(i => i.FullName?.GetName() == itemNameToSelect); if (itemToSelect != null) { newSelectedItem = new AbsolutePath(_timelessContentProvider, itemToSelect); } } } LastDeepestSelectedPath = FullName.CreateSafe(PathHelper.GetLongerPath(LastDeepestSelectedPath?.Path, newSelectedItem.Path.Path)); return newSelectedItem; } public async Task SetCurrentLocation(IContainer newLocation) { _future.Clear(); await SetCurrentLocation(newLocation, true); } private async Task SetCurrentLocation(IContainer newLocation, bool addToHistory) { _setCurrentLocationCancellationTokenSource?.Cancel(); _setCurrentLocationCancellationTokenSource = new CancellationTokenSource(); if (addToHistory && newLocation.FullName is { } fullName && (_history.Count == 0 || _history.Last() != fullName)) { _history.PushFront(fullName); } await _currentLocation.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token); if (newLocation.FullName != null) { _tabEvents.OnLocationChanged(this, newLocation); } } public async Task ForceSetCurrentLocation(IContainer newLocation) { _setCurrentLocationCancellationTokenSource?.Cancel(); _setCurrentLocationCancellationTokenSource = new CancellationTokenSource(); await _currentLocationForced.SetValue(newLocation, _setCurrentLocationCancellationTokenSource.Token); if (newLocation.FullName != null) { _tabEvents.OnLocationChanged(this, newLocation); } } public async Task GoBackAsync() { if (_history.Count < 2) return; var currentLocationFullName = _history.PopFront(); _future.PushFront(currentLocationFullName); var lastLocationFullName = _history.First(); var container = (IContainer) await _timelessContentProvider.GetItemByFullNameAsync( lastLocationFullName, PointInTime.Present); await SetCurrentLocation(container, false); } public async Task GoForwardAsync() { if (_future.Count == 0) return; var fullName = _future.PopFront(); _history.PushFront(fullName); var container = (IContainer) await _timelessContentProvider.GetItemByFullNameAsync( fullName, PointInTime.Present); await SetCurrentLocation(container, false); } public async Task SetSelectedItem(AbsolutePath newSelectedItem) { if (_currentRequestItem.Value is {} v && v.Path == newSelectedItem.Path) return; await _currentRequestItem.SetValue(newSelectedItem); } public void AddItemFilter(ItemFilter filter) => _itemFilters.Add(filter); public void RemoveItemFilter(ItemFilter filter) => _itemFilters.Remove(filter); public void RemoveItemFilter(string name) { var itemsToRemove = _itemFilters.Where(t => t.Name == name).ToList(); _itemFilters.RemoveMany(itemsToRemove); } public async Task OpenSelected() { if (_currentSelectedItemCached == null) return; var resolvedSelectedItem = await _currentSelectedItemCached.TimelessProvider.GetItemByFullNameAsync(_currentSelectedItemCached.Path, _currentPointInTime); if (resolvedSelectedItem is not IContainer resolvedContainer) return; await SetCurrentLocation(resolvedContainer); } public void Dispose() => _currentLocation.Dispose(); }