diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IRefreshSmoothnessCalculator.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IRefreshSmoothnessCalculator.cs new file mode 100644 index 0000000..7655601 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IRefreshSmoothnessCalculator.cs @@ -0,0 +1,9 @@ +namespace FileTime.App.Core.Services; + +public interface IRefreshSmoothnessCalculator +{ + TimeSpan RefreshDelay { get; } + void RegisterChange(); + void RegisterChange(DateTime changeTime); + void RecalculateSmoothness(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IItemViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IItemViewModel.cs index 3f373e1..a2e94fe 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IItemViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IItemViewModel.cs @@ -1,3 +1,4 @@ +using DeclarativeProperty; using FileTime.App.Core.Models; using FileTime.App.Core.Models.Enums; using FileTime.Core.Models; @@ -10,9 +11,9 @@ public interface IItemViewModel : IInitable>? DisplayName { get; set; } string? DisplayNameText { get; set; } - IObservable? IsSelected { get; set; } + IDeclarativeProperty IsSelected { get; set; } IObservable? IsMarked { get; set; } - IObservable IsAlternative { get; } + IDeclarativeProperty IsAlternative { get; } IObservable ViewMode { get; set; } DateTime? CreatedAt { get; set; } string? Attributes { get; set; } diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs index 8f0bf32..09b512b 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/ITabViewModel.cs @@ -1,4 +1,6 @@ +using System.Collections.ObjectModel; +using DeclarativeProperty; using DynamicData; using FileTime.Core.Models; using FileTime.Core.Services; @@ -11,19 +13,13 @@ public interface ITabViewModel : IInitable, IDisposable ITab? Tab { get; } int TabNumber { get; } IObservable IsSelected { get; } - IObservable CurrentLocation { get; } - IObservable CurrentSelectedItem { get; } - IObservable>?> CurrentItems { get; } + IDeclarativeProperty CurrentLocation { get; } + IDeclarativeProperty CurrentSelectedItem { get; } + IDeclarativeProperty CurrentSelectedItemAsContainer { get; } + IDeclarativeProperty?> CurrentItems { get; } IObservable> MarkedItems { get; } - IObservable>?> SelectedsChildren { get; } - IObservable>?> ParentsChildren { get; } - BindedCollection? CurrentItemsCollection { get; } - BindedCollection? SelectedsChildrenCollection { get; } - BindedCollection? ParentsChildrenCollection { get; } - IObservable?> CurrentItemsCollectionObservable { get; } - IObservable?> ParentsChildrenCollectionObservable { get; } - IObservable?> SelectedsChildrenCollectionObservable { get; } - IContainer? CachedCurrentLocation { get; } + IDeclarativeProperty> SelectedsChildren { get; } + IDeclarativeProperty> ParentsChildren { get; } void ClearMarkedItems(); void RemoveMarkedItem(FullName fullName); diff --git a/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj b/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj index 71ef6f1..12f9db2 100644 --- a/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj +++ b/src/AppCommon/FileTime.App.Core/FileTime.App.Core.csproj @@ -14,6 +14,7 @@ + @@ -21,6 +22,7 @@ + diff --git a/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs b/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs index 62c9c9a..63ef489 100644 --- a/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs @@ -81,7 +81,7 @@ public class TabPersistenceService : ITabPersistenceService { if (!File.Exists(_settingsPath)) { - CreateEmptyTab(); + await CreateEmptyTab(); return; } @@ -99,12 +99,12 @@ public class TabPersistenceService : ITabPersistenceService _logger.LogError(e, "Unknown exception while restoring app state"); } - CreateEmptyTab(); + await CreateEmptyTab(); - void CreateEmptyTab() + async Task CreateEmptyTab() { - var tab = _serviceProvider.GetInitableResolver(_localContentProvider) - .GetRequiredService(); + var tab = await _serviceProvider.GetAsyncInitableResolver(_localContentProvider) + .GetRequiredServiceAsync(); var tabViewModel = _serviceProvider.GetInitableResolver(tab, 1).GetRequiredService(); _appState.AddTab(tabViewModel); @@ -159,8 +159,8 @@ public class TabPersistenceService : ITabPersistenceService if (container == null) continue; - var tabToLoad = _serviceProvider.GetInitableResolver(container) - .GetRequiredService(); + var tabToLoad = await _serviceProvider.GetAsyncInitableResolver(container) + .GetRequiredServiceAsync(); var tabViewModel = _serviceProvider.GetInitableResolver(tabToLoad, tab.Number) .GetRequiredService(); @@ -205,7 +205,7 @@ public class TabPersistenceService : ITabPersistenceService var tabStates = new List(); foreach (var tab in _appState.Tabs) { - var currentLocation = tab.CachedCurrentLocation; + var currentLocation = tab.CurrentLocation.Value; if (currentLocation is null) continue; tabStates.Add(new TabState(currentLocation.FullName!, tab.TabNumber)); } diff --git a/src/AppCommon/FileTime.App.Core/Services/RefreshSmoothnessCalculator.cs b/src/AppCommon/FileTime.App.Core/Services/RefreshSmoothnessCalculator.cs new file mode 100644 index 0000000..6182c20 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core/Services/RefreshSmoothnessCalculator.cs @@ -0,0 +1,115 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace FileTime.App.Core.Services; + +public sealed class RefreshSmoothnessCalculator : IRefreshSmoothnessCalculator, INotifyPropertyChanged +{ + private const int MaxSampleTimeInSeconds = 10; + private const int MaxDelayBetweenRefreshes = 400; + private readonly TimeSpan _maxDelay = TimeSpan.FromSeconds(MaxSampleTimeInSeconds); + private readonly TimeSpan _defaultRefreshDelay = TimeSpan.FromMilliseconds(200); + private readonly Queue _changeTimes = new(); + private readonly object _lock = new(); + private TimeSpan _refreshDelay; + + public TimeSpan RefreshDelay + { + get => _refreshDelay; + private set + { + if (value.Equals(_refreshDelay)) return; + _refreshDelay = value; + OnPropertyChanged(); + } + } + + public RefreshSmoothnessCalculator() + { + _refreshDelay = _defaultRefreshDelay; + } + + public void RegisterChange() => RegisterChange(DateTime.Now); + + public void RegisterChange(DateTime changeTime) + { + lock (_lock) + { + if (_changeTimes.Count > 0 && Math.Abs((_changeTimes.Last() - changeTime).TotalMilliseconds) < 5) return; + CleanList(DateTime.Now); + _changeTimes.Enqueue(changeTime); + } + } + + private void CleanList(DateTime now) + { + while (_changeTimes.Count > 0) + { + var item = _changeTimes.Peek(); + if (now - item < _maxDelay) return; + _changeTimes.Dequeue(); + } + } + + public void RecalculateSmoothness() + { + lock (_lock) + { + if (_changeTimes.Count < 2) + { + RefreshDelay = _defaultRefreshDelay; + return; + } + + var now = DateTime.Now; + CleanList(now); + + var queue = new Queue(_changeTimes); + var values = new List<(double score, double weight)>(queue.Count - 1); + + var previousChangeTime = queue.Dequeue(); + var biggestDelay = now - previousChangeTime; + while (queue.Count > 0) + { + var changeTime = queue.Dequeue(); + + var (score, weight) = CalculateScoreAndWeight(changeTime, previousChangeTime, biggestDelay); + + values.Add((score, weight)); + + previousChangeTime = changeTime; + } + + var combinedScore = values.Sum(i => i.weight * i.score) / values.Sum(i => i.weight); + + var normalizedCombinedScore = (combinedScore * 1.2 - 0.1); + + if (normalizedCombinedScore < 0) normalizedCombinedScore = 0; + else if (normalizedCombinedScore > 1) normalizedCombinedScore = 1; + + var finalDelay = normalizedCombinedScore * MaxDelayBetweenRefreshes; + + RefreshDelay = TimeSpan.FromMilliseconds(finalDelay); + + (double score, double weight) CalculateScoreAndWeight(DateTime changeTime, DateTime previousChangeTime, TimeSpan biggestDelay) + { + var delayToPrevious = changeTime - previousChangeTime; + var delayToNow = now - changeTime; + + var toNowRatio = (delayToNow.TotalMilliseconds / biggestDelay.TotalMilliseconds); + var score = 1 - (delayToPrevious.TotalMilliseconds / biggestDelay.TotalMilliseconds); + var weight = 1 - toNowRatio; + + if (score < 0) score = 0; + else if (score > 1) score = 1; + + return (score, weight); + } + } + } + + public event PropertyChangedEventHandler? PropertyChanged; + + private void OnPropertyChanged([CallerMemberName] string? propertyName = null) + => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs index b8a4aa9..545e2be 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ItemManipulationUserCommandHandlerService.cs @@ -1,6 +1,7 @@ using System.Reactive.Linq; using System.Reactive.Subjects; using System.Text.RegularExpressions; +using DeclarativeProperty; using DynamicData; using FileTime.App.Core.Interactions; using FileTime.App.Core.Models; @@ -25,7 +26,7 @@ namespace FileTime.App.Core.Services.UserCommandHandler; public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServiceBase { private ITabViewModel? _selectedTab; - private IItemViewModel? _currentSelectedItem; + private IDeclarativeProperty? _currentSelectedItem; private readonly IUserCommandHandlerService _userCommandHandlerService; private readonly IClipboardService _clipboardService; private readonly IUserCommunicationService _userCommunicationService; @@ -35,7 +36,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi private readonly IServiceProvider _serviceProvider; private readonly ISystemClipboardService _systemClipboardService; private readonly BindedCollection? _markedItems; - private IContainer? _currentLocation; + private IDeclarativeProperty? _currentLocation; public ItemManipulationUserCommandHandlerService( IAppState appState, @@ -84,7 +85,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi { list.AddRange(_markedItems!.Collection!); } - else if(_currentSelectedItem?.BaseItem?.FullName is { } selectedItemName) + else if(_currentSelectedItem?.Value?.BaseItem?.FullName is { } selectedItemName) { list.Add(selectedItemName); } @@ -106,20 +107,20 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi private async Task PasteFilesFromClipboardAsync(TransportMode mode) { - if (_currentLocation?.FullName is not { }) return; + if (_currentLocation?.Value?.FullName is not { }) return; var files = (await _systemClipboardService.GetFilesAsync()).ToList(); var copyCommandFactory = _serviceProvider.GetRequiredService(); - var copyCommand = copyCommandFactory.GenerateCommand(files, mode, _currentLocation.FullName); + var copyCommand = copyCommandFactory.GenerateCommand(files, mode, _currentLocation.Value.FullName); await AddCommandAsync(copyCommand); } private async Task MarkItemAsync() { - if (_selectedTab == null || _currentSelectedItem?.BaseItem?.FullName == null) return; + if (_selectedTab == null || _currentSelectedItem?.Value?.BaseItem?.FullName == null) return; - _selectedTab.ToggleMarkedItem(_currentSelectedItem.BaseItem.FullName); + _selectedTab.ToggleMarkedItem(_currentSelectedItem.Value.BaseItem.FullName); await _userCommandHandlerService.HandleCommandAsync(MoveCursorDownCommand.Instance); } @@ -137,9 +138,9 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi _selectedTab?.ClearMarkedItems(); } - else if (_currentSelectedItem?.BaseItem != null) + else if (_currentSelectedItem?.Value?.BaseItem != null) { - var item = _currentSelectedItem.BaseItem; + var item = _currentSelectedItem.Value.BaseItem; _clipboardService.AddContent( item.FullName ?? throw new ArgumentException($"{nameof(item.FullName)} can not be null.", nameof(item)) @@ -168,7 +169,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi //TODO: check _currentLocation?.FullName var commandFactory = (ITransportationCommandFactory) _serviceProvider.GetRequiredService(_clipboardService.CommandFactoryType); - var command = commandFactory.GenerateCommand(_clipboardService.Content, mode, _currentLocation?.FullName); + var command = commandFactory.GenerateCommand(_clipboardService.Content, mode, _currentLocation?.Value?.FullName); _clipboardService.Clear(); @@ -186,10 +187,10 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi //TODO: message on empty result var newContainerName = containerNameInput.Value; - if (_currentLocation?.FullName is null || newContainerName is null) return; + if (_currentLocation?.Value?.FullName is null || newContainerName is null) return; var command = _serviceProvider - .GetInitableResolver(_currentLocation.FullName, newContainerName) + .GetInitableResolver(_currentLocation.Value.FullName, newContainerName) .GetRequiredService(); await AddCommandAsync(command); } @@ -203,10 +204,10 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi //TODO: message on empty result var newContainerName = containerNameInput.Value; - if (_currentLocation?.FullName is null || newContainerName is null) return; + if (_currentLocation?.Value?.FullName is null || newContainerName is null) return; var command = _serviceProvider - .GetInitableResolver(_currentLocation.FullName, newContainerName) + .GetInitableResolver(_currentLocation.Value.FullName, newContainerName) .GetRequiredService(); await AddCommandAsync(command); } @@ -342,9 +343,9 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi } else { - if (_currentSelectedItem?.BaseItem?.FullName is null) return; + if (_currentSelectedItem?.Value?.BaseItem?.FullName is null) return; - var item = await _timelessContentProvider.GetItemByFullNameAsync(_currentSelectedItem.BaseItem.FullName, PointInTime.Present); + var item = await _timelessContentProvider.GetItemByFullNameAsync(_currentSelectedItem.Value.BaseItem.FullName, PointInTime.Present); if (item is null) return; @@ -410,11 +411,11 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi { itemsToDelete = new List(_markedItems!.Collection!); } - else if (_currentSelectedItem?.BaseItem?.FullName is not null) + else if (_currentSelectedItem?.Value?.BaseItem?.FullName is not null) { itemsToDelete = new List() { - _currentSelectedItem.BaseItem.FullName + _currentSelectedItem.Value.BaseItem.FullName }; } @@ -425,7 +426,7 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi var resolvedOnlyItem = await _timelessContentProvider.GetItemByFullNameAsync(itemsToDelete[0], PointInTime.Present); if (resolvedOnlyItem is IContainer {AllowRecursiveDeletion: true} onlyContainer - && onlyContainer.ItemsCollection.Any()) + && onlyContainer.Items.Count > 0) { questionText = $"The container '{onlyContainer.DisplayName}' is not empty. Proceed with delete?"; } @@ -461,8 +462,6 @@ public class ItemManipulationUserCommandHandlerService : UserCommandHandlerServi _selectedTab?.ClearMarkedItems(); } - private async Task AddCommandAsync(ICommand command) - { - await _commandScheduler.AddCommand(command); - } + private async Task AddCommandAsync(ICommand command) + => await _commandScheduler.AddCommand(command); } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs index cf685e6..602dee3 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs @@ -1,3 +1,5 @@ +using System.Collections.ObjectModel; +using DeclarativeProperty; using FileTime.App.CommandPalette.Services; using FileTime.App.Core.Extensions; using FileTime.App.Core.Models.Enums; @@ -25,9 +27,9 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase private readonly IFrequencyNavigationService _frequencyNavigationService; private readonly ICommandPaletteService _commandPaletteService; private ITabViewModel? _selectedTab; - private IContainer? _currentLocation; - private IItemViewModel? _currentSelectedItem; - private IEnumerable _currentItems = Enumerable.Empty(); + private IDeclarativeProperty? _currentLocation; + private IDeclarativeProperty? _currentSelectedItem; + private IDeclarativeProperty?>? _currentItems; private ViewMode _viewMode; public NavigationUserCommandHandlerService( @@ -121,7 +123,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase private async Task GoToRoot() { - var root = _currentLocation; + var root = _currentLocation?.Value; if (root is null) return; while (true) @@ -138,21 +140,25 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase private async Task GoToProvider() { - if (_currentLocation is null) return; + if (_currentLocation?.Value is null) return; await _userCommandHandlerService.HandleCommandAsync( - new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, _currentLocation.Provider))); + new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, _currentLocation.Value.Provider))); } private async Task Refresh() { - if (_currentLocation?.FullName is null) return; + if (_currentLocation?.Value?.FullName is null) return; var refreshedItem = - await _timelessContentProvider.GetItemByFullNameAsync(_currentLocation.FullName, PointInTime.Present); + await _timelessContentProvider.GetItemByFullNameAsync(_currentLocation.Value.FullName, PointInTime.Present); if (refreshedItem is not IContainer refreshedContainer) return; - _selectedTab?.Tab?.ForceSetCurrentLocation(refreshedContainer); + + if (_selectedTab?.Tab is { } tab) + { + await tab.ForceSetCurrentLocation(refreshedContainer); + } } private async Task OpenContainer(OpenContainerCommand command) @@ -160,91 +166,100 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase var resolvedPath = await command.Path.ResolveAsync(); if (resolvedPath is not IContainer resolvedContainer) return; - _selectedTab?.Tab?.SetCurrentLocation(resolvedContainer); + if (_selectedTab?.Tab is { } tab) + { + await tab.SetCurrentLocation(resolvedContainer); + } } - private Task OpenSelected() + private async Task OpenSelected() { - if (_currentSelectedItem is not IContainerViewModel containerViewModel || containerViewModel.Container is null) - return Task.CompletedTask; + if (_currentSelectedItem?.Value is not IContainerViewModel containerViewModel || containerViewModel.Container is null) + return; _appState.RapidTravelText = ""; - _selectedTab?.Tab?.SetCurrentLocation(containerViewModel.Container); - return Task.CompletedTask; + if (_selectedTab?.Tab is { } tab) + { + await tab.SetCurrentLocation(containerViewModel.Container); + } } private async Task GoUp() { - if (_currentLocation?.Parent is not AbsolutePath parentPath || + if (_currentLocation?.Value?.Parent is not AbsolutePath parentPath || await parentPath.ResolveAsyncSafe() is not IContainer newContainer) { return; } _appState.RapidTravelText = ""; - _selectedTab?.Tab?.SetCurrentLocation(newContainer); - } - - private Task MoveCursorDown() - { - SelectNewSelectedItem(items => - items.SkipWhile(i => !i.EqualsTo(_currentSelectedItem)).Skip(1).FirstOrDefault()); - return Task.CompletedTask; - } - - private Task MoveCursorUp() - { - SelectNewSelectedItem(items => items.TakeWhile(i => !i.EqualsTo(_currentSelectedItem)).LastOrDefault()); - return Task.CompletedTask; - } - - private Task MoveCursorDownPage() - { - SelectNewSelectedItem(items => + if (_selectedTab?.Tab is { } tab) { - var relevantItems = items.SkipWhile(i => !i.EqualsTo(_currentSelectedItem)).ToList(); + await tab.SetCurrentLocation(newContainer); + } + } + + private async Task MoveCursorDown() + => await SelectNewSelectedItem(items => + { + if (_currentSelectedItem?.Value == null) return items.FirstOrDefault(); + return items.SkipWhile(i => !i.EqualsTo(_currentSelectedItem?.Value)).Skip(1).FirstOrDefault(); + }); + + private async Task MoveCursorUp() + => await SelectNewSelectedItem(items => + { + if (_currentSelectedItem?.Value == null) return items.LastOrDefault(); + return items.TakeWhile(i => !i.EqualsTo(_currentSelectedItem?.Value)).LastOrDefault(); + }); + + private async Task MoveCursorDownPage() + => await SelectNewSelectedItem(items => + { + var relevantItems = _currentSelectedItem?.Value is null + ? items.ToList() + : items.SkipWhile(i => !i.EqualsTo(_currentSelectedItem.Value)).ToList(); + var fallBackItems = relevantItems.Take(PageSize + 1).Reverse(); var preferredItems = relevantItems.Skip(PageSize + 1); return preferredItems.Concat(fallBackItems).FirstOrDefault(); }); - return Task.CompletedTask; - } - private Task MoveCursorUpPage() - { - SelectNewSelectedItem(items => + private async Task MoveCursorUpPage() + => await SelectNewSelectedItem(items => { - var relevantItems = items.TakeWhile(i => !i.EqualsTo(_currentSelectedItem)).Reverse().ToList(); + var relevantItems = _currentSelectedItem?.Value is null + ? items.Reverse().ToList() + : items.TakeWhile(i => !i.EqualsTo(_currentSelectedItem?.Value)).Reverse().ToList(); + var fallBackItems = relevantItems.Take(PageSize).Reverse(); var preferredItems = relevantItems.Skip(PageSize); + return preferredItems.Concat(fallBackItems).FirstOrDefault(); }); - return Task.CompletedTask; - } - private Task MoveCursorToFirst() + private async Task MoveCursorToFirst() + => await SelectNewSelectedItem(items => items.FirstOrDefault()); + + private async Task MoveCursorToLast() + => await SelectNewSelectedItem(items => items.LastOrDefault()); + + private Task SelectNewSelectedItem(Func, IItemViewModel?> getNewSelected) { - SelectNewSelectedItem(items => items.FirstOrDefault()); + if (_selectedTab is null || _currentItems?.Value is null) return Task.CompletedTask; + + var newSelectedItem = getNewSelected(_currentItems.Value); + if (newSelectedItem == null) return Task.CompletedTask; + + if (_selectedTab.Tab is { } tab) + { + tab.SetSelectedItem(newSelectedItem.ToAbsolutePath(_timelessContentProvider)); + } + return Task.CompletedTask; } - private Task MoveCursorToLast() - { - SelectNewSelectedItem(items => items.LastOrDefault()); - return Task.CompletedTask; - } - - private void SelectNewSelectedItem(Func, IItemViewModel?> getNewSelected) - { - if (_selectedTab is null || _currentLocation is null) return; - - var newSelectedItem = getNewSelected(_currentItems); - if (newSelectedItem == null) return; - - _selectedTab.Tab?.SetSelectedItem(newSelectedItem.ToAbsolutePath(_timelessContentProvider)); - } - private Task EnterRapidTravel() { _appState.SwitchViewMode(ViewMode.RapidTravel); @@ -257,7 +272,7 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase return Task.CompletedTask; } - private Task SwitchToTab(SwitchToTabCommand command) + private async Task SwitchToTab(SwitchToTabCommand command) { var number = command.TabNumber; var tabViewModel = _appState.Tabs.FirstOrDefault(t => t.TabNumber == number); @@ -269,8 +284,8 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase } else if (tabViewModel == null) { - var tab = _serviceProvider.GetInitableResolver(_currentLocation ?? _localContentProvider) - .GetRequiredService(); + var tab = await _serviceProvider.GetAsyncInitableResolver(_currentLocation?.Value ?? _localContentProvider) + .GetRequiredServiceAsync(); var newTabViewModel = _serviceProvider.GetInitableResolver(tab, number).GetRequiredService(); _appState.AddTab(newTabViewModel); @@ -279,12 +294,10 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase if (_viewMode == ViewMode.RapidTravel) { - _userCommandHandlerService.HandleCommandAsync(ExitRapidTravelCommand.Instance); + await _userCommandHandlerService.HandleCommandAsync(ExitRapidTravelCommand.Instance); } _appState.SetSelectedTab(tabViewModel!); - - return Task.CompletedTask; } private Task CloseTab() diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs index 0e8b15b..a9561dd 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using DeclarativeProperty; using FileTime.App.Core.UserCommand; using FileTime.App.Core.ViewModels; using FileTime.App.Search; @@ -19,8 +20,8 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase private readonly ITimelessContentProvider _timelessContentProvider; private readonly IUserCommandHandlerService _userCommandHandlerService; private readonly IContentAccessorFactory _contentAccessorFactory; - private IContainer? _currentLocation; - private IItemViewModel? _currentSelectedItem; + private IDeclarativeProperty? _currentLocation; + private IDeclarativeProperty? _currentSelectedItem; public ToolUserCommandHandlerService( IAppState appState, @@ -53,7 +54,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase private async Task CopyBase64() { - var item = _currentSelectedItem?.BaseItem; + var item = _currentSelectedItem?.Value?.BaseItem; if (item?.Type != AbsolutePathType.Element || item is not IElement element) return; var contentReader = await _contentAccessorFactory.GetContentReaderFactory(element.Provider).CreateContentReaderAsync(element); @@ -72,7 +73,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase private async Task Search(SearchCommand searchCommand) { - if (_currentLocation is null) return; + if (_currentLocation?.Value is null) return; var searchQuery = searchCommand.SearchText; if (string.IsNullOrEmpty(searchQuery)) @@ -102,21 +103,21 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase _ => throw new ArgumentOutOfRangeException() }; - var searchTask = await _searchManager.StartSearchAsync(searchMatcher, _currentLocation); + var searchTask = await _searchManager.StartSearchAsync(searchMatcher, _currentLocation.Value); var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, searchTask.SearchContainer)); await _userCommandHandlerService.HandleCommandAsync(openContainerCommand); } private async Task CopyNativePath() { - if (_currentSelectedItem?.BaseItem?.NativePath is null) return; - await _systemClipboardService.CopyToClipboardAsync(_currentSelectedItem.BaseItem.NativePath.Path); + if (_currentSelectedItem?.Value?.BaseItem?.NativePath is null) return; + await _systemClipboardService.CopyToClipboardAsync(_currentSelectedItem.Value.BaseItem.NativePath.Path); } private Task OpenInDefaultFileExplorer() { - if (_currentLocation?.NativePath is null) return Task.CompletedTask; - Process.Start("explorer.exe", "\"" + _currentLocation.NativePath.Path + "\""); + if (_currentLocation?.Value?.NativePath is null) return Task.CompletedTask; + Process.Start("explorer.exe", "\"" + _currentLocation.Value.NativePath.Path + "\""); return Task.CompletedTask; } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs index 13d36bc..cec3b9e 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/UserCommandHandlerServiceBase.cs @@ -1,4 +1,6 @@ +using System.Collections.ObjectModel; using System.Reactive.Linq; +using DeclarativeProperty; using DynamicData; using FileTime.App.Core.ViewModels; using FileTime.Core.Models; @@ -44,14 +46,14 @@ public abstract class UserCommandHandlerServiceBase : IUserCommandHandler protected IDisposable SaveSelectedTab(Action handler) => RunWithAppState(appState => appState.SelectedTab.Subscribe(handler)); - protected IDisposable SaveCurrentSelectedItem(Action handler) - => RunWithAppState(appState => appState.SelectedTab.Select(t => t == null ? Observable.Return(null) : t.CurrentSelectedItem).Switch().Subscribe(handler)); + protected IDisposable SaveCurrentSelectedItem(Action?> handler) + => RunWithAppState(appState => appState.SelectedTab.Select(t => t?.CurrentSelectedItem).Subscribe(handler)); - protected IDisposable SaveCurrentLocation(Action handler) - => RunWithAppState(appState => appState.SelectedTab.Select(t => t == null ? Observable.Return(null) : t.CurrentLocation).Switch().Subscribe(handler)); + protected IDisposable SaveCurrentLocation(Action?> handler) + => RunWithAppState(appState => appState.SelectedTab.Select(t => t?.CurrentLocation).Subscribe(handler)); - protected IDisposable SaveCurrentItems(Action> handler) - => RunWithAppState(appState => appState.SelectedTab.Select(t => t?.CurrentItemsCollectionObservable ?? Observable.Return((IEnumerable?) Enumerable.Empty())).Switch().Subscribe(i => handler(i ?? Enumerable.Empty()))); + protected IDisposable SaveCurrentItems(Action?>?> handler) + => RunWithAppState(appState => appState.SelectedTab.Select(t => t?.CurrentItems).Subscribe(handler)); protected IDisposable SaveMarkedItems(Action> handler) => RunWithAppState(appState => appState.SelectedTab.Select(t => t == null ? Observable.Empty>() : t.MarkedItems).Switch().Subscribe(handler)); diff --git a/src/AppCommon/FileTime.App.Core/Startup.cs b/src/AppCommon/FileTime.App.Core/Startup.cs index 9250367..0d22be8 100644 --- a/src/AppCommon/FileTime.App.Core/Startup.cs +++ b/src/AppCommon/FileTime.App.Core/Startup.cs @@ -26,6 +26,7 @@ public static class Startup serviceCollection.TryAddSingleton(); serviceCollection.TryAddSingleton(); serviceCollection.TryAddSingleton(); + serviceCollection.TryAddSingleton(); return serviceCollection .AddCommandHandlers() diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ContainerViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ContainerViewModel.cs index 593289a..4c8fdd1 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ContainerViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ContainerViewModel.cs @@ -10,12 +10,11 @@ public partial class ContainerViewModel : ItemViewModel, IContainerViewModel { public IContainer? Container => BaseItem as IContainer; - public ContainerViewModel(IItemNameConverterService itemNameConverterService, IAppState appState) : base(itemNameConverterService, appState) + public ContainerViewModel(IItemNameConverterService itemNameConverterService, IAppState appState) + : base(itemNameConverterService, appState) { } - public void Init(IContainer item, ITabViewModel parentTab, ItemViewModelType itemViewModelType) - { - Init((IItem)item, parentTab, itemViewModelType); - } + public void Init(IContainer item, ITabViewModel parentTab, ItemViewModelType itemViewModelType) + => Init((IItem)item, parentTab, itemViewModelType); } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs index 11263b2..769c731 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs @@ -1,5 +1,6 @@ using System.ComponentModel; using System.Reactive.Linq; +using DeclarativeProperty; using DynamicData; using FileTime.App.Core.Models; using FileTime.App.Core.Models.Enums; @@ -18,32 +19,23 @@ public abstract partial class ItemViewModel : IItemViewModel { private ITabViewModel? _parentTab; - [Property] - private IItem? _baseItem; + [Property] private IItem? _baseItem; - [Property] - private IObservable>? _displayName; + [Property] private IObservable>? _displayName; - [Property] - private string? _displayNameText; + [Property] private string? _displayNameText; - [Property] - private IObservable? _isSelected; + [Property] private IDeclarativeProperty _isSelected; - [Property] - private IObservable? _isMarked; + [Property] private IObservable? _isMarked; - [Property] - private IObservable _viewMode; + [Property] private IObservable _viewMode; - [Property] - private DateTime? _createdAt; + [Property] private DateTime? _createdAt; - [Property] - private string? _attributes; + [Property] private string? _attributes; - [Property] - private IObservable _isAlternative; + [Property] private IDeclarativeProperty _isAlternative; public void Init(IItem item, ITabViewModel parentTab, ItemViewModelType itemViewModelType) { @@ -51,9 +43,9 @@ public abstract partial class ItemViewModel : IItemViewModel var sourceCollection = itemViewModelType switch { - ItemViewModelType.Main => parentTab.CurrentItemsCollectionObservable, - ItemViewModelType.Parent => parentTab.ParentsChildrenCollectionObservable, - ItemViewModelType.SelectedChild => parentTab.SelectedsChildrenCollectionObservable, + ItemViewModelType.Main => parentTab.CurrentItems, + ItemViewModelType.Parent => parentTab.ParentsChildren, + ItemViewModelType.SelectedChild => parentTab.SelectedsChildren, _ => throw new InvalidEnumArgumentException() }; @@ -66,10 +58,10 @@ public abstract partial class ItemViewModel : IItemViewModel : Observable.Return(false); IsSelected = itemViewModelType is ItemViewModelType.Main - ? parentTab.CurrentSelectedItem.Select(EqualsTo) - : Observable.Return(IsInDeepestPath()); + ? parentTab.CurrentSelectedItem.Map(EqualsTo) + : new DeclarativeProperty(IsInDeepestPath()); - IsAlternative = sourceCollection.Select(c => c?.Index().FirstOrDefault(i => EqualsTo(i.Value)).Key % 2 == 0); + IsAlternative = sourceCollection.Map(c => c?.Index().FirstOrDefault(i => EqualsTo(i.Value)).Key % 2 == 0); ViewMode = Observable.CombineLatest(IsMarked, IsSelected, IsAlternative, GenerateViewMode).Throttle(TimeSpan.FromMilliseconds(10)); Attributes = item.Attributes; diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs index fab67b0..b2b72d0 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs @@ -1,4 +1,7 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; using System.Reactive.Linq; +using DeclarativeProperty; using DynamicData; using DynamicData.Binding; using FileTime.App.Core.Extensions; @@ -10,6 +13,9 @@ using FileTime.Core.Models.Extensions; using FileTime.Core.Services; using InitableService; using MvvmGen; +using ObservableComputations; +using IContainer = FileTime.Core.Models.IContainer; +using static System.DeferTools; namespace FileTime.App.Core.ViewModels; @@ -23,36 +29,23 @@ public partial class TabViewModel : ITabViewModel private readonly SourceList _markedItems = new(); private readonly List _disposables = new(); private bool _disposed; + private OcConsumer? _currentItemsConsumer; + private OcConsumer? _selectedsChildrenConsumer; + private OcConsumer? _parentsChildrenConsumer; public ITab? Tab { get; private set; } public int TabNumber { get; private set; } public IObservable IsSelected { get; } - public IObservable CurrentLocation { get; private set; } = null!; - public IObservable CurrentSelectedItem { get; private set; } = null!; - public IObservable>?> CurrentItems { get; private set; } = null!; + public IDeclarativeProperty CurrentLocation { get; private set; } + public IDeclarativeProperty CurrentSelectedItem { get; private set; } + public IDeclarativeProperty CurrentSelectedItemAsContainer { get; private set; } + public IDeclarativeProperty?> CurrentItems { get; private set; } public IObservable> MarkedItems { get; } - public IObservable>?> SelectedsChildren { get; private set; } = null!; - public IObservable>?> ParentsChildren { get; private set; } = null!; + public IDeclarativeProperty?> SelectedsChildren { get; private set; } + public IDeclarativeProperty?> ParentsChildren { get; private set; } - public IObservable?> CurrentItemsCollectionObservable { get; private set; } = - null!; - - public IObservable?> ParentsChildrenCollectionObservable { get; private set; } = - null!; - - public IObservable?> - SelectedsChildrenCollectionObservable - { get; private set; } = null!; - - [Property] private BindedCollection? _currentItemsCollection; - - [Property] private BindedCollection? _parentsChildrenCollection; - - [Property] private BindedCollection? _selectedsChildrenCollection; - - public IContainer? CachedCurrentLocation { get; private set; } public TabViewModel( IServiceProvider serviceProvider, @@ -76,13 +69,19 @@ public partial class TabViewModel : ITabViewModel tab.AddToDisposables(_disposables); - CurrentLocation = tab.CurrentLocation.AsObservable(); - CurrentLocation.Subscribe(l => CachedCurrentLocation = l).AddToDisposables(_disposables); + CurrentLocation = tab.CurrentLocation; CurrentItems = tab.CurrentItems - .Select(items => items?.Transform(i => MapItemToViewModel(i, ItemViewModelType.Main))) - .Publish(null) - .RefCount(); + .Map((items, _) => + Task.FromResult?>( + items?.Selecting( + i => MapItemToViewModel(i, ItemViewModelType.Main) + ) + ) + ); + using var _ = Defer( + () => CurrentItems.Subscribe(c => UpdateConsumer(c, ref _currentItemsConsumer)) + ); /*CurrentSelectedItem = Observable.CombineLatest( @@ -100,30 +99,65 @@ public partial class TabViewModel : ITabViewModel .Publish(null) .RefCount();*/ - CurrentSelectedItem = + CurrentSelectedItem = DeclarativePropertyHelpers.CombineLatest( + tab.CurrentSelectedItem, + CurrentItems.Watch, IItemViewModel>(), + (currentSelectedItem, currentItems) => + Task.FromResult(currentItems?.FirstOrDefault(i => i.BaseItem?.FullName?.Path == currentSelectedItem?.Path.Path)) + ); + + CurrentSelectedItemAsContainer = CurrentSelectedItem.Map(i => i as IContainerViewModel); + //CurrentSelectedItem = tab.CurrentSelectedItem.Map((item, _) => Task.FromResult(CurrentItems.Value?.FirstOrDefault(i => i.BaseItem?.FullName?.Path == item?.Path.Path))); + + /*CurrentSelectedItem = Observable.CombineLatest( CurrentItems, tab.CurrentSelectedItem, (currentItems, currentSelectedItemPath) => CurrentItemsCollection?.Collection?.FirstOrDefault(i => i.BaseItem?.FullName?.Path == currentSelectedItemPath?.Path.Path) - ) + ) .Publish(null) - .RefCount(); + .RefCount();*/ - SelectedsChildren = InitSelectedsChildren(); - ParentsChildren = InitParentsChildren(); + SelectedsChildren = CurrentSelectedItem + .Debounce(TimeSpan.FromMilliseconds(200), resetTimer: true) + .DistinctUntilChanged() + .Map(item => + { + if (item is not IContainerViewModel {Container: { } container}) + return (ObservableCollection?) null; - CurrentItemsCollectionObservable = InitCollection(CurrentItems); - SelectedsChildrenCollectionObservable = InitCollection(SelectedsChildren); - ParentsChildrenCollectionObservable = InitCollection(ParentsChildren); + var items = container + .Items + .Selecting(i => MapItem(i)) + .Ordering(i => i.Type) + .ThenOrdering(i => i.Name) + .Selecting(i => MapItemToViewModel(i, ItemViewModelType.SelectedChild)); - CurrentItemsCollection = new(CurrentItems); - ParentsChildrenCollection = new(ParentsChildren); - SelectedsChildrenCollection = new(SelectedsChildren); + return items; + }); + using var __ = Defer(() => + SelectedsChildren.Subscribe(c => UpdateConsumer(c, ref _selectedsChildrenConsumer)) + ); - tab.CurrentLocation.Subscribe((_) => _markedItems.Clear()).AddToDisposables(_disposables); + ParentsChildren = CurrentLocation.Map(async (item, _) => + { + if (item is null || item.Parent is null) return (ObservableCollection?) null; + var parent = (IContainer) await item.Parent.ResolveAsync(itemInitializationSettings: new ItemInitializationSettings(true)); - IObservable>?> InitSelectedsChildren() + var items = parent.Items + .Selecting(i => MapItem(i)) + .Selecting(i => MapItemToViewModel(i, ItemViewModelType.SelectedChild)); + + return items; + }); + using var ___ = Defer(() => + ParentsChildren.Subscribe(c => UpdateConsumer(c, ref _parentsChildrenConsumer)) + ); + + tab.CurrentLocation.Subscribe(_ => _markedItems.Clear()).AddToDisposables(_disposables); + + /*IObservable>?> InitSelectedsChildren() { var currentSelectedItemThrottled = CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250)).Publish(null).RefCount(); @@ -135,16 +169,16 @@ public partial class TabViewModel : ITabViewModel .Select(c => c.Container!.Items) .Select(i => i - ?.TransformAsync(MapItem) + ?.TransformAsync(MapItemAsync) .Transform(i => MapItemToViewModel(i, ItemViewModelType.SelectedChild)) .Sort(SortItems()) ), currentSelectedItemThrottled .Where(c => c is null or not IContainerViewModel) - .Select(_ => (IObservable>?)null) + .Select(_ => (IObservable>?) null) ) /*.ObserveOn(_rxSchedulerService.GetWorkerScheduler()) - .SubscribeOn(_rxSchedulerService.GetUIScheduler())*/ + .SubscribeOn(_rxSchedulerService.GetUIScheduler())#1# .Publish(null) .RefCount(); } @@ -160,35 +194,34 @@ public partial class TabViewModel : ITabViewModel return Observable.Merge( parentThrottled .Where(p => p is not null) - .Select(p => Observable.FromAsync(async () => (IContainer)await p!.ResolveAsync())) + .Select(p => Observable.FromAsync(async () => (IContainer) await p!.ResolveAsync())) .Switch() .Select(p => p.Items) .Select(items => items - ?.TransformAsync(MapItem) + ?.TransformAsync(MapItemAsync) .Transform(i => MapItemToViewModel(i, ItemViewModelType.Parent)) .Sort(SortItems()) ), parentThrottled .Where(p => p is null) - .Select(_ => (IObservable>?)null) + .Select(_ => (IObservable>?) null) ) /*.ObserveOn(_rxSchedulerService.GetWorkerScheduler()) - .SubscribeOn(_rxSchedulerService.GetUIScheduler())*/ + .SubscribeOn(_rxSchedulerService.GetUIScheduler())#1# .Publish(null) .RefCount(); - } + }*/ + } - IObservable?> InitCollection( - IObservable>?> source) - { - return source - .Select(c => - c != null ? c.ToCollection() : Observable.Return((IReadOnlyCollection?)null)) - .Switch() - .Publish(null) - .RefCount(); - } + + static void UpdateConsumer(ObservableCollection? collection, ref OcConsumer? consumer) + { + if (collection is not IComputing computing) return; + + consumer?.Dispose(); + consumer = new OcConsumer(); + computing.For(consumer); } private static SortExpressionComparer SortItems() @@ -197,7 +230,14 @@ public partial class TabViewModel : ITabViewModel .Ascending(i => i.BaseItem?.Type ?? AbsolutePathType.Unknown) .ThenByAscending(i => i.DisplayNameText?.ToLower() ?? ""); - private static async Task MapItem(AbsolutePath item) + private static IItem MapItem(AbsolutePath item) + { + var t = Task.Run(async () => await MapItemAsync(item)); + t.Wait(); + return t.Result; + } + + private static async Task MapItemAsync(AbsolutePath item) => await item.ResolveAsync(forceResolve: true, itemInitializationSettings: new ItemInitializationSettings(true)); diff --git a/src/AppCommon/FileTime.App.Search/SearchTask.cs b/src/AppCommon/FileTime.App.Search/SearchTask.cs index 17a1be5..be2c324 100644 --- a/src/AppCommon/FileTime.App.Search/SearchTask.cs +++ b/src/AppCommon/FileTime.App.Search/SearchTask.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using DynamicData; using FileTime.Core.ContentAccess; using FileTime.Core.Enums; @@ -11,8 +12,8 @@ public class SearchTask : ISearchTask private readonly IContainer _baseContainer; private readonly ISearchMatcher _matcher; private readonly Container _container; - private readonly SourceList _exceptions = new(); - private readonly SourceCache _items = new(p => p.Path.Path); + private readonly ObservableCollection _exceptions = new(); + private readonly ObservableCollection _items = new(); private readonly SemaphoreSlim _searchingLock = new(1, 1); private bool _isSearching; private static int _searchId = 1; @@ -43,9 +44,9 @@ public class SearchTask : ISearchTask contentProvider, false, PointInTime.Present, - _exceptions.Connect(), + _exceptions, new ReadOnlyExtensionCollection(new ExtensionCollection()), - _items.Connect().StartWithEmpty() + _items ); } @@ -78,7 +79,7 @@ public class SearchTask : ISearchTask private async Task TraverseTree(IContainer container) { - var items = container.ItemsCollection.ToList(); + var items = container.Items.ToList(); var childContainers = new List(); @@ -87,7 +88,7 @@ public class SearchTask : ISearchTask var item = await itemPath.ResolveAsync(); if (await _matcher.IsItemMatchAsync(item)) { - _items.AddOrUpdate(itemPath); + _items.Add(itemPath); } if (item is IContainer childContainer) diff --git a/src/Core/FileTime.Core.Abstraction/FileTime.Core.Abstraction.csproj b/src/Core/FileTime.Core.Abstraction/FileTime.Core.Abstraction.csproj index 58d8690..c81ed0c 100644 --- a/src/Core/FileTime.Core.Abstraction/FileTime.Core.Abstraction.csproj +++ b/src/Core/FileTime.Core.Abstraction/FileTime.Core.Abstraction.csproj @@ -21,6 +21,7 @@ + diff --git a/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs b/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs index e9ad1c9..c31937e 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/IContainer.cs @@ -5,8 +5,7 @@ namespace FileTime.Core.Models; public interface IContainer : IItem { - IObservable> Items { get; } - ReadOnlyObservableCollection ItemsCollection { get; } + ObservableCollection Items { get; } IObservable IsLoading { get; } bool? IsLoaded { get; } Task WaitForLoaded(CancellationToken token = default); diff --git a/src/Core/FileTime.Core.Abstraction/Models/IItem.cs b/src/Core/FileTime.Core.Abstraction/Models/IItem.cs index 186e6b6..5f768c0 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/IItem.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/IItem.cs @@ -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> Exceptions { get; } + ObservableCollection Exceptions { get; } ReadOnlyExtensionCollection Extensions { get; } T? GetExtension() => (T?)Extensions.FirstOrDefault(i => i is T); diff --git a/src/Core/FileTime.Core.Abstraction/Services/ITab.cs b/src/Core/FileTime.Core.Abstraction/Services/ITab.cs index 8b44f16..b7c05e3 100644 --- a/src/Core/FileTime.Core.Abstraction/Services/ITab.cs +++ b/src/Core/FileTime.Core.Abstraction/Services/ITab.cs @@ -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, IDisposable +public interface ITab : IAsyncInitable, IDisposable { - IObservable CurrentLocation { get; } - IObservable CurrentSelectedItem { get; } - IObservable>?> CurrentItems { get; } + public IDeclarativeProperty CurrentLocation { get; } + public IDeclarativeProperty?> CurrentItems { get; } + public IDeclarativeProperty 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); } \ No newline at end of file diff --git a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs index 5fbb549..ad103d2 100644 --- a/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs +++ b/src/Core/FileTime.Core.Command/Copy/CopyCommand.cs @@ -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)); diff --git a/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs b/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs index 0dd2d4f..b53f319 100644 --- a/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs +++ b/src/Core/FileTime.Core.Command/Create/CreateItemBase.cs @@ -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 diff --git a/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs b/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs index 5f1243a..3f98915 100644 --- a/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs +++ b/src/Core/FileTime.Core.Command/Delete/DeleteCommand.cs @@ -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 ); diff --git a/src/Core/FileTime.Core.Command/Helper.cs b/src/Core/FileTime.Core.Command/Helper.cs index 78ecbe7..f528f9b 100644 --- a/src/Core/FileTime.Core.Command/Helper.cs +++ b/src/Core/FileTime.Core.Command/Helper.cs @@ -7,7 +7,7 @@ public static class Helper public static async Task 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) diff --git a/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs b/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs index 1ae89d3..1630ee4 100644 --- a/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs +++ b/src/Core/FileTime.Core.CommandHandlers/StreamCopyCommandHandler.cs @@ -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); diff --git a/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs b/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs index 7ad9e87..165417c 100644 --- a/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs +++ b/src/Core/FileTime.Core.ContentAccess/ContentProviderBase.cs @@ -11,14 +11,9 @@ namespace FileTime.Core.ContentAccess; public abstract class ContentProviderBase : IContentProvider { private readonly ReadOnlyExtensionCollection _extensions; - private readonly IObservable> _items; - - protected SourceCache Items { get; } = new(p => p.Path.Path); - public ReadOnlyObservableCollection ItemsCollection { get; } + public ObservableCollection Items { get; } protected ExtensionCollection Extensions { get; } - IObservable> 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 Exceptions { get; } = new(); - IObservable> IItem.Exceptions => Exceptions.Connect(); + public ObservableCollection 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(); } public virtual Task OnEnter() => Task.CompletedTask; diff --git a/src/Core/FileTime.Core.Models/Container.cs b/src/Core/FileTime.Core.Models/Container.cs index e89a279..f4b3c48 100644 --- a/src/Core/FileTime.Core.Models/Container.cs +++ b/src/Core/FileTime.Core.Models/Container.cs @@ -23,17 +23,10 @@ public record Container( IContentProvider Provider, bool AllowRecursiveDeletion, PointInTime PointInTime, - IObservable> Exceptions, + ObservableCollection Exceptions, ReadOnlyExtensionCollection Extensions, - IObservable> Items) : IContainer + ObservableCollection Items) : IContainer { - - private readonly Lazy> _itemsCollectionLazy = - new(() => - { - Items.Bind(out var items).Subscribe(); - return items; - }); private readonly CancellationTokenSource _loadingCancellationTokenSource = new(); private readonly BehaviorSubject _isLoading = new(false); @@ -42,8 +35,6 @@ public record Container( public bool? IsLoaded { get; private set; } public AbsolutePathType Type => AbsolutePathType.Container; - public ReadOnlyObservableCollection ItemsCollection => _itemsCollectionLazy.Value; - public async Task WaitForLoaded(CancellationToken token = default) { while (IsLoaded != true) await Task.Delay(1, token); diff --git a/src/Core/FileTime.Core.Models/Element.cs b/src/Core/FileTime.Core.Models/Element.cs index 5796694..5b65a33 100644 --- a/src/Core/FileTime.Core.Models/Element.cs +++ b/src/Core/FileTime.Core.Models/Element.cs @@ -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> Exceptions, + ObservableCollection Exceptions, ReadOnlyExtensionCollection Extensions) : IElement { public AbsolutePathType Type => AbsolutePathType.Element; diff --git a/src/Core/FileTime.Core.Services/FileTime.Core.Services.csproj b/src/Core/FileTime.Core.Services/FileTime.Core.Services.csproj index 88f8a13..5b9f1b7 100644 --- a/src/Core/FileTime.Core.Services/FileTime.Core.Services.csproj +++ b/src/Core/FileTime.Core.Services/FileTime.Core.Services.csproj @@ -11,12 +11,15 @@ + + + diff --git a/src/Core/FileTime.Core.Services/Tab.cs b/src/Core/FileTime.Core.Services/Tab.cs index 199fb44..3d37c38 100644 --- a/src/Core/FileTime.Core.Services/Tab.cs +++ b/src/Core/FileTime.Core.Services/Tab.cs @@ -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 _currentLocation = new(null); + private readonly IRefreshSmoothnessCalculator _refreshSmoothnessCalculator; + private readonly DeclarativeProperty _currentLocation = new(null); private readonly BehaviorSubject _currentLocationForced = new(null); - private readonly BehaviorSubject _currentSelectedItem = new(null); + private readonly DeclarativeProperty _currentRequestItem = new(null); private readonly SourceList _itemFilters = new(); private AbsolutePath? _currentSelectedItemCached; private PointInTime _currentPointInTime; + private OcConsumer? _currentItemsConsumer; + private CancellationTokenSource? _setCurrentLocationCancellationTokenSource; + private CancellationTokenSource? _setCurrentItemCancellationTokenSource; - public IObservable CurrentLocation { get; } - public IObservable>?> CurrentItems { get; } - public IObservable CurrentSelectedItem { get; } + public IDeclarativeProperty CurrentLocation { get; } + public IDeclarativeProperty?> CurrentItems { get; } + public IDeclarativeProperty 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?) null + : container.Items.Selecting(i => MapItem(i)); + return Task.FromResult(items); + } + ) /*.Watch, 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>?) null) ) .Publish(null) - .RefCount(); + .RefCount();*/ + 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); - 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(ObservableCollection? 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 SortItems() //TODO: Order => SortExpressionComparer .Ascending(i => i.Type) .ThenByAscending(i => i.DisplayName.ToLower()); - private async Task 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 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(); } } \ No newline at end of file diff --git a/src/FileTime.sln b/src/FileTime.sln index 93f9599..7cf5ff5 100644 --- a/src/FileTime.sln +++ b/src/FileTime.sln @@ -81,6 +81,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.App.FuzzyPanel.Abs EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FileTime.GuiApp.DesignPreview", "GuiApp\Avalonia\FileTime.GuiApp.DesignPreview\FileTime.GuiApp.DesignPreview.csproj", "{D0EC224E-F043-4657-BD6A-1ADE52DFF8B5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeclarativeProperty", "Library\DeclarativeProperty\DeclarativeProperty.csproj", "{D5C85B69-6345-4FAA-A00C-3A73BA677664}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Defer", "Library\Defer\Defer.csproj", "{609FFADA-C221-4E41-B377-C6AF56C0A900}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -211,6 +215,14 @@ Global {D0EC224E-F043-4657-BD6A-1ADE52DFF8B5}.Debug|Any CPU.Build.0 = Debug|Any CPU {D0EC224E-F043-4657-BD6A-1ADE52DFF8B5}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0EC224E-F043-4657-BD6A-1ADE52DFF8B5}.Release|Any CPU.Build.0 = Release|Any CPU + {D5C85B69-6345-4FAA-A00C-3A73BA677664}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5C85B69-6345-4FAA-A00C-3A73BA677664}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5C85B69-6345-4FAA-A00C-3A73BA677664}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5C85B69-6345-4FAA-A00C-3A73BA677664}.Release|Any CPU.Build.0 = Release|Any CPU + {609FFADA-C221-4E41-B377-C6AF56C0A900}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {609FFADA-C221-4E41-B377-C6AF56C0A900}.Debug|Any CPU.Build.0 = Debug|Any CPU + {609FFADA-C221-4E41-B377-C6AF56C0A900}.Release|Any CPU.ActiveCfg = Release|Any CPU + {609FFADA-C221-4E41-B377-C6AF56C0A900}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -248,6 +260,8 @@ Global {7FDCE43D-D084-4539-B797-8A72D4DD610D} = {A5291117-3001-498B-AC8B-E14F71F72570} {7690E4EA-DA2A-45B4-AD83-80EE07A05169} = {A5291117-3001-498B-AC8B-E14F71F72570} {D0EC224E-F043-4657-BD6A-1ADE52DFF8B5} = {01F231DE-4A65-435F-B4BB-77EE5221890C} + {D5C85B69-6345-4FAA-A00C-3A73BA677664} = {07CA18AA-B85D-4DEE-BB86-F569F6029853} + {609FFADA-C221-4E41-B377-C6AF56C0A900} = {07CA18AA-B85D-4DEE-BB86-F569F6029853} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF} diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs index 7fd44ba..e79996a 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/ViewModels/IGuiAppState.cs @@ -1,4 +1,5 @@ using System.Collections.ObjectModel; +using DeclarativeProperty; using FileTime.App.Core.ViewModels; using FileTime.Core.Models; using FileTime.GuiApp.Configuration; @@ -12,7 +13,7 @@ public interface IGuiAppState : IAppState bool IsAllShortcutVisible { get; set; } bool NoCommandFound { get; set; } List PossibleCommands { get; set; } - BindedCollection RootDriveInfos { get; set; } + ObservableCollection RootDriveInfos { get; set; } IReadOnlyList Places { get; set; } ObservableCollection PopupTexts { get; } IObservable ActivePanel { get; } diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs index 09c3df2..4251426 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.CustomImpl/ViewModels/GuiAppState.cs @@ -27,7 +27,7 @@ public partial class GuiAppState : AppStateBase, IGuiAppState, IDisposable [Property] private List _possibleCommands = new(); - [Property] private BindedCollection _rootDriveInfos = new(); + [Property] private ObservableCollection _rootDriveInfos = new(); [Property] private IReadOnlyList _places = new List(); diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/GuiAppStatePreview.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/GuiAppStatePreview.cs index 6d46097..0f53a65 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/GuiAppStatePreview.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/GuiAppStatePreview.cs @@ -10,7 +10,7 @@ using FileTime.GuiApp.ViewModels; namespace FileTime.GuiApp.DesignPreview.Services; -public class GuiAppStatePreview : IGuiAppState +/*public class GuiAppStatePreview : IGuiAppState { public GuiAppStatePreview() { @@ -19,7 +19,7 @@ public class GuiAppStatePreview : IGuiAppState CurrentSelectedTab = tab; var tabs = new ObservableCollection(new [] {tab}); - Tabs = new ReadOnlyObservableCollection(tabs); + Tabs = new ObservableCollection(tabs); SearchText = new BehaviorSubject(null); ViewMode = new BehaviorSubject(FileTime.App.Core.Models.Enums.ViewMode.Default); @@ -30,7 +30,7 @@ public class GuiAppStatePreview : IGuiAppState PopupTexts = new ObservableCollection(); } - public ReadOnlyObservableCollection Tabs { get; } + public ObservableCollection Tabs { get; } public IObservable SelectedTab { get; } public IObservable SearchText { get; } public IObservable ViewMode { get; } @@ -56,4 +56,4 @@ public class GuiAppStatePreview : IGuiAppState public ObservableCollection PopupTexts { get; } public IObservable ActivePanel { get; } public void SetActivePanel(GuiPanel newPanel) => throw new NotImplementedException(); -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/ItemPreview.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/ItemPreview.cs index 463bf93..ad9123b 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/ItemPreview.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/ItemPreview.cs @@ -1,4 +1,5 @@ -using DynamicData; +using System.Collections.ObjectModel; +using DynamicData; using FileTime.Core.Enums; using FileTime.Core.Models; using FileTime.Core.Timeline; @@ -11,7 +12,7 @@ public class ItemPreview static ItemPreview() { - var exceptions = new SourceList(); + var exceptions = new ObservableCollection(); CurrentContainer = new Container( "HomePreview", "HomePreview", @@ -32,9 +33,9 @@ public class ItemPreview null!, false, PointInTime.Present, - exceptions.Connect(), + exceptions, new ExtensionCollection().AsReadOnly(), - new SourceCache(a => a.Path.Path).Connect() + new ObservableCollection() ); } @@ -58,7 +59,7 @@ public class ItemPreview "attr", null!, PointInTime.Present, - new SourceList().Connect(), + new ObservableCollection(), new ExtensionCollection().AsReadOnly() ); } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabPreview.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabPreview.cs index 4098162..e4f4d0d 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabPreview.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabPreview.cs @@ -6,7 +6,7 @@ using FileTime.Core.Services; namespace FileTime.GuiApp.DesignPreview.Services; -public class TabPreview : ITab +/*public class TabPreview : ITab { public TabPreview() { @@ -46,4 +46,4 @@ public class TabPreview : ITab public void Dispose() { } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabViewModelPreview.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabViewModelPreview.cs index 14d75de..715f279 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabViewModelPreview.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.DesignPreview/Services/TabViewModelPreview.cs @@ -9,7 +9,7 @@ using FileTime.Core.Services; namespace FileTime.GuiApp.DesignPreview.Services; -public class TabViewModelPreview : ITabViewModel +/*public class TabViewModelPreview : ITabViewModel { private static readonly ItemNameConverterService _itemNameConverterService = new(); @@ -115,4 +115,4 @@ public class TabViewModelPreview : ITabViewModel public void Dispose() { } -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml index 76dbd1b..c4f67b4 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Styles.axaml @@ -1,6 +1,4 @@ - + @@ -21,6 +19,9 @@ +