From f4d361f767ab2caac9052717503163487442aaf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81d=C3=A1m=20Kov=C3=A1cs?= Date: Thu, 3 Aug 2023 00:00:45 +0200 Subject: [PATCH] Container size scan, improvements --- .../IContainerSizeScanContainer.cs | 13 - .../ISizeItem.cs | 6 +- .../ISizePreviewItem.cs | 8 +- .../ISizeScanContainer.cs | 17 + .../ISizeScanElement.cs | 8 + .../ISizeScanTask.cs | 4 +- .../ChildContainerPreview.cs | 15 + .../ChildElementPreview.cs | 15 + .../ContainerPreview.cs | 36 ++ ...otProvider.cs => ContainerScanProvider.cs} | 16 +- .../ContainerSizeContainerPreview.cs | 22 -- .../ContainerSizePreviewProvider.cs | 17 - .../ContainerSizeScanElement.cs | 29 -- .../PreviewProvider.cs | 17 + ...eScanContainer.cs => SizeScanContainer.cs} | 37 +- .../SizeScanElement.cs | 32 ++ .../SizeScanTask.cs | 89 ++++- .../Startup.cs | 4 +- .../Interactions/DoubleTextPreview.cs | 2 +- .../Models/Enums/ItemAttributeType.cs | 9 - .../Models/Traits/ISizeProvider.cs | 8 + .../Services/IItemPreviewService.cs | 3 +- .../ViewModels/IAppState.cs | 1 + .../IContainerSizeContainerViewModel.cs | 10 - .../ViewModels/IElementViewModel.cs | 7 +- .../ViewModels/IItemViewModel.cs | 2 +- .../Services/ItemPreviewService.cs | 18 +- .../Persistence/TabPersistenceService.cs | 3 +- .../NavigationUserCommandHandlerService.cs | 6 +- .../ToolUserCommandHandlerService.cs | 2 +- src/AppCommon/FileTime.App.Core/Startup.cs | 1 - .../ViewModels/AppStateBase.cs | 10 + .../ContainerSizeContainerViewModel.cs | 20 -- .../ViewModels/ElementViewModel.cs | 6 +- .../ViewModels/FileViewModel.cs | 5 +- .../ViewModels/ItemViewModel.cs | 34 +- .../ViewModels/TabViewModel.cs | 2 +- .../Services/FrequencyNavigationService.cs | 2 +- .../Models/ContainerEscapeResult.cs | 17 + .../ContainerTraits/IEscHandlerContainer.cs | 6 + .../IStatusProviderContainer.cs | 8 + .../FileTime.Core.Abstraction/Models/IItem.cs | 1 - .../Models/ReadOnlyExtensionCollection.cs | 5 + src/Core/FileTime.Core.Models/Container.cs | 1 - .../Converters/DebugConverter.cs | 11 + .../Converters/FormatSizeConverter.cs | 31 +- .../Converters/IsTypeConverter.cs | 25 ++ .../Converters/ItemSizeToBrushConverter.cs | 41 +++ .../Converters/ItemSizeToSizeConverter.cs | 19 + .../ItemViewModelIsAttributeTypeConverter.cs | 36 -- .../FileTime.GuiApp/Helper/ColorHelper.cs | 43 +++ .../Resources/Converters.axaml | 19 +- .../Services/DefaultModeKeyInputHandler.cs | 14 +- .../FileTime.GuiApp/Views/ItemView.axaml | 152 +++----- .../FileTime.GuiApp/Views/MainWindow.axaml | 325 +++++++++--------- .../CombineLatestProperty.cs | 48 +++ .../DeclarativeProperty.cs | 8 + 57 files changed, 814 insertions(+), 532 deletions(-) delete mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/IContainerSizeScanContainer.cs create mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanContainer.cs create mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanElement.cs create mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner/ChildContainerPreview.cs create mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner/ChildElementPreview.cs create mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerPreview.cs rename src/AppCommon/FileTime.App.ContainerSizeScanner/{ContainerScanSnapshotProvider.cs => ContainerScanProvider.cs} (86%) delete mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeContainerPreview.cs delete mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizePreviewProvider.cs delete mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanElement.cs create mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner/PreviewProvider.cs rename src/AppCommon/FileTime.App.ContainerSizeScanner/{ContainerSizeScanContainer.cs => SizeScanContainer.cs} (65%) create mode 100644 src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanElement.cs delete mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/Models/Enums/ItemAttributeType.cs create mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/Models/Traits/ISizeProvider.cs delete mode 100644 src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerSizeContainerViewModel.cs delete mode 100644 src/AppCommon/FileTime.App.Core/ViewModels/ContainerSizeContainerViewModel.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Models/ContainerEscapeResult.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Models/ContainerTraits/IEscHandlerContainer.cs create mode 100644 src/Core/FileTime.Core.Abstraction/Models/ContainerTraits/IStatusProviderContainer.cs create mode 100644 src/GuiApp/Avalonia/FileTime.GuiApp/Converters/DebugConverter.cs create mode 100644 src/GuiApp/Avalonia/FileTime.GuiApp/Converters/IsTypeConverter.cs create mode 100644 src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemSizeToBrushConverter.cs create mode 100644 src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemSizeToSizeConverter.cs delete mode 100644 src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemViewModelIsAttributeTypeConverter.cs create mode 100644 src/GuiApp/Avalonia/FileTime.GuiApp/Helper/ColorHelper.cs diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/IContainerSizeScanContainer.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/IContainerSizeScanContainer.cs deleted file mode 100644 index 1b972cc..0000000 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/IContainerSizeScanContainer.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Collections.ObjectModel; -using DeclarativeProperty; -using FileTime.Core.Models; - -namespace FileTime.App.ContainerSizeScanner; - -public interface IContainerSizeScanContainer : ISizeItem, IContainer -{ - public Task AddSizeSourceAsync(IDeclarativeProperty sizeElement); - ObservableCollection ChildContainers { get; } - IContainer RealContainer { get; init; } - IDeclarativeProperty Size { get; } -} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeItem.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeItem.cs index cd06cb9..abfc50f 100644 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeItem.cs +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeItem.cs @@ -1,8 +1,8 @@ -using FileTime.Core.Models; +using FileTime.App.Core.Models.Traits; +using FileTime.Core.Models; namespace FileTime.App.ContainerSizeScanner; -public interface ISizeItem : IItem +public interface ISizeItem : IItem, ISizeProvider { - } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizePreviewItem.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizePreviewItem.cs index 9941462..f8cfdfc 100644 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizePreviewItem.cs +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizePreviewItem.cs @@ -1,8 +1,10 @@ -using FileTime.App.Core.ViewModels.ItemPreview; +using DeclarativeProperty; +using FileTime.App.Core.ViewModels.ItemPreview; namespace FileTime.App.ContainerSizeScanner; -public interface ISizePreviewItem : IItemPreviewViewModel +public interface ISizePreviewItem { - + IDeclarativeProperty Size { get; } + string Name { get; } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanContainer.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanContainer.cs new file mode 100644 index 0000000..43ada17 --- /dev/null +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanContainer.cs @@ -0,0 +1,17 @@ +using System.Collections.ObjectModel; +using DeclarativeProperty; +using FileTime.Core.Models; +using FileTime.Core.Models.ContainerTraits; + +namespace FileTime.App.ContainerSizeScanner; + +public interface ISizeScanContainer : ISizeItem, IContainer, IStatusProviderContainer +{ + public Task AddSizeSourceAsync(IDeclarativeProperty sizeElement); + ObservableCollection ChildContainers { get; } + ObservableCollection ChildElements { get; } + ObservableCollection SizeItems { get; } + IContainer RealContainer { get; init; } + Task StartLoadingAsync(); + Task StopLoadingAsync(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanElement.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanElement.cs new file mode 100644 index 0000000..2fb6eca --- /dev/null +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanElement.cs @@ -0,0 +1,8 @@ +using FileTime.Core.Models; + +namespace FileTime.App.ContainerSizeScanner; + +public interface ISizeScanElement : ISizeItem, IElement +{ + +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanTask.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanTask.cs index 56e3693..a34ab74 100644 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanTask.cs +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner.Abstractions/ISizeScanTask.cs @@ -5,6 +5,8 @@ namespace FileTime.App.ContainerSizeScanner; public interface ISizeScanTask : IInitable { - IContainerSizeScanContainer SizeContainer { get; } + ISizeScanContainer SizeSizeScanContainer { get; } + bool IsRunning { get; } void Start(); + void Stop(); } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ChildContainerPreview.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ChildContainerPreview.cs new file mode 100644 index 0000000..9974a43 --- /dev/null +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/ChildContainerPreview.cs @@ -0,0 +1,15 @@ +using DeclarativeProperty; + +namespace FileTime.App.ContainerSizeScanner; + +public class ChildContainerPreview : ISizePreviewItem +{ + public ChildContainerPreview(ISizeScanContainer container) + { + Name = container.Name; + Size = container.Size; + } + + public string Name { get; } + public IDeclarativeProperty Size { get; } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ChildElementPreview.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ChildElementPreview.cs new file mode 100644 index 0000000..3be8d52 --- /dev/null +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/ChildElementPreview.cs @@ -0,0 +1,15 @@ +using DeclarativeProperty; + +namespace FileTime.App.ContainerSizeScanner; + +public class ChildElementPreview : ISizePreviewItem +{ + public ChildElementPreview(ISizeScanElement element) + { + Name = element.Name; + Size = element.Size; + } + + public string Name { get; } + public IDeclarativeProperty Size { get; } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerPreview.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerPreview.cs new file mode 100644 index 0000000..a307aa8 --- /dev/null +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerPreview.cs @@ -0,0 +1,36 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; +using FileTime.App.Core.ViewModels.ItemPreview; +using ObservableComputations; + +namespace FileTime.App.ContainerSizeScanner; + +public class ContainerPreview : IItemPreviewViewModel, IDisposable +{ + private readonly OcConsumer _topItemsConsumer = new(); + public const string PreviewName = "SizePreviewContainer"; + public string Name => PreviewName; + + public ObservableCollection TopItems { get; } + + public ContainerPreview(ISizeScanContainer sizeScanContainer) + { + TopItems = sizeScanContainer + .SizeItems + .Ordering(c => c.Size.Value, ListSortDirection.Descending) + .Taking(0, 10) + .Selecting(i => CreatePreviewItem(i)) + .For(_topItemsConsumer); + } + + private ISizePreviewItem CreatePreviewItem(ISizeItem sizeItem) + => sizeItem switch + { + ISizeScanContainer container => new ChildContainerPreview(container), + ISizeScanElement element => new ChildElementPreview(element), + _ => throw new ArgumentOutOfRangeException(nameof(sizeItem)) + }; + + public void Dispose() + => _topItemsConsumer.Dispose(); +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerScanSnapshotProvider.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerScanProvider.cs similarity index 86% rename from src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerScanSnapshotProvider.cs rename to src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerScanProvider.cs index 0b5ebf0..0b690fe 100644 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerScanSnapshotProvider.cs +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerScanProvider.cs @@ -7,14 +7,14 @@ using InitableService; namespace FileTime.App.ContainerSizeScanner; -public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScanSnapshotProvider +public class ContainerScanProvider : ContentProviderBase, IContainerScanSnapshotProvider { private readonly ITimelessContentProvider _timelessContentProvider; private readonly IServiceProvider _serviceProvider; private readonly List _sizeScanTasks = new(); internal const string ContentProviderName = "container-size-scan"; - public ContainerScanSnapshotProvider( + public ContainerScanProvider( ITimelessContentProvider timelessContentProvider, IServiceProvider serviceProvider) : base(ContentProviderName, timelessContentProvider) @@ -36,7 +36,7 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan var pathParts = fullName.Path.Split(Constants.SeparatorChar); - var item = _sizeScanTasks.FirstOrDefault(t => t.SizeContainer.Name == pathParts[1])?.SizeContainer; + var item = _sizeScanTasks.FirstOrDefault(t => t.SizeSizeScanContainer.Name == pathParts[1])?.SizeSizeScanContainer; if (pathParts.Length == 2) return item ?? throw new ItemNotFoundException(fullName); @@ -49,10 +49,10 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan if (item is not null) { - var container = item.ChildContainers.FirstOrDefault(c => c.Name == pathParts[^1]); - if (container is not null) return container; + var childItem = item.SizeItems.FirstOrDefault(c => c.Name == pathParts[^1]); + if (childItem is not null) return childItem; - var childName = item.RealContainer.FullName?.GetChild(pathParts[^1]); + /*var childName = item.RealContainer.FullName?.GetChild(pathParts[^1]); if (childName is null) throw new ItemNotFoundException(fullName); return await _timelessContentProvider.GetItemByFullNameAsync( @@ -61,7 +61,7 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan forceResolve, forceResolvePathType, itemInitializationSettings - ); + );*/ } throw new ItemNotFoundException(fullName); @@ -106,7 +106,7 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan _sizeScanTasks.Add(searchTask); searchTask.Start(); - Items.Add(new AbsolutePath(_timelessContentProvider, searchTask.SizeContainer)); + Items.Add(new AbsolutePath(_timelessContentProvider, searchTask.SizeSizeScanContainer)); return searchTask; } diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeContainerPreview.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeContainerPreview.cs deleted file mode 100644 index 5a22f63..0000000 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeContainerPreview.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.ObjectModel; -using FileTime.App.Core.ViewModels.ItemPreview; -using ObservableComputations; - -namespace FileTime.App.ContainerSizeScanner; - -public class ContainerSizeContainerPreview : ISizePreviewItem -{ - public const string PreviewName = "SizePreviewContainer"; - public string Name => PreviewName; - - public ObservableCollection Items { get; } - - public ContainerSizeContainerPreview(IContainerSizeScanContainer container) - { - Items = container - .ChildContainers - .Ordering(c => c.Size) - .Taking(0, 10) - .Selecting(); - } -} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizePreviewProvider.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizePreviewProvider.cs deleted file mode 100644 index 9a9feab..0000000 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizePreviewProvider.cs +++ /dev/null @@ -1,17 +0,0 @@ -using FileTime.App.Core.Services; -using FileTime.App.Core.ViewModels.ItemPreview; -using FileTime.Core.Models; - -namespace FileTime.App.ContainerSizeScanner; - -public class ContainerSizePreviewProvider : IItemPreviewProvider -{ - public bool CanHandle(IItem item) => item is ContainerSizeScanContainer; - - public Task CreatePreviewAsync(IItem item) - { - if(item is not ContainerSizeScanContainer container) throw new NotSupportedException(); - - return Task.FromResult((IItemPreviewViewModel)new ContainerSizeContainerPreview(container)); - } -} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanElement.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanElement.cs deleted file mode 100644 index 1fb0597..0000000 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanElement.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.ObjectModel; -using FileTime.Core.ContentAccess; -using FileTime.Core.Enums; -using FileTime.Core.Models; -using FileTime.Core.Timeline; - -namespace FileTime.App.ContainerSizeScanner; - -public record ContainerSizeScanElement( - string Name, - string DisplayName, - FullName FullName, - NativePath NativePath, - AbsolutePath? Parent, - bool IsHidden, - bool IsExists, - DateTime? CreatedAt, - SupportsDelete CanDelete, - bool CanRename, - string? Attributes, - IContentProvider Provider, - PointInTime PointInTime, - ObservableCollection Exceptions, - ReadOnlyExtensionCollection Extensions) : IElement -{ - public AbsolutePathType Type => AbsolutePathType.Element; - - public IItem WithParent(AbsolutePath parent) => this with { Parent = parent }; -} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/PreviewProvider.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/PreviewProvider.cs new file mode 100644 index 0000000..0181b48 --- /dev/null +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/PreviewProvider.cs @@ -0,0 +1,17 @@ +using FileTime.App.Core.Services; +using FileTime.App.Core.ViewModels.ItemPreview; +using FileTime.Core.Models; + +namespace FileTime.App.ContainerSizeScanner; + +public class PreviewProvider : IItemPreviewProvider +{ + public bool CanHandle(IItem item) => item is SizeScanContainer; + + public Task CreatePreviewAsync(IItem item) + { + if (item is not SizeScanContainer container) throw new NotSupportedException(); + + return Task.FromResult((IItemPreviewViewModel) new ContainerPreview(container)); + } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanContainer.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanContainer.cs similarity index 65% rename from src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanContainer.cs rename to src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanContainer.cs index 6140a29..7645a64 100644 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner/ContainerSizeScanContainer.cs +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanContainer.cs @@ -5,12 +5,13 @@ using DeclarativeProperty; using FileTime.Core.ContentAccess; using FileTime.Core.Enums; using FileTime.Core.Models; +using FileTime.Core.Models.ContainerTraits; using FileTime.Core.Timeline; namespace FileTime.App.ContainerSizeScanner; //TODO: create readonly version -public class ContainerSizeScanContainer : IContainerSizeScanContainer +public record SizeScanContainer : ISizeScanContainer, IEscHandlerContainer { private readonly ReadOnlyExtensionCollection _readOnlyExtensions; private readonly BehaviorSubject _isLoading = new(false); @@ -19,20 +20,20 @@ public class ContainerSizeScanContainer : IContainerSizeScanContainer public required string DisplayName { get; init; } public required FullName? FullName { get; init; } public required NativePath? NativePath { get; init; } - public AbsolutePath? Parent { get; init; } + public required AbsolutePath? Parent { get; init; } + public required IContentProvider Provider { get; init; } public bool IsHidden => false; public bool IsExists => true; - public DateTime? CreatedAt { get; } + public DateTime? CreatedAt { get; } = DateTime.Now; public SupportsDelete CanDelete => SupportsDelete.True; public bool CanRename => false; - public IContentProvider Provider { get; } public string? Attributes => null; public AbsolutePathType Type => AbsolutePathType.Container; public PointInTime PointInTime => PointInTime.Present; public ObservableCollection Exceptions { get; } = new(); public ExtensionCollection Extensions { get; } = new(); ReadOnlyExtensionCollection IItem.Extensions => _readOnlyExtensions; - public IItem WithParent(AbsolutePath parent) => throw new NotImplementedException(); + public IItem WithParent(AbsolutePath parent) => this with {Parent = parent}; public ObservableCollection Items { get; } = new(); public IObservable IsLoading { get; } @@ -41,18 +42,21 @@ public class ContainerSizeScanContainer : IContainerSizeScanContainer public bool AllowRecursiveDeletion => false; - public IDeclarativeProperty Size => _size; - public ObservableCollection ChildContainers { get; } = new(); + public IDeclarativeProperty Size { get; } + + public ObservableCollection ChildContainers { get; } = new(); + public ObservableCollection ChildElements { get; } = new(); + public ObservableCollection SizeItems { get; } = new(); public required IContainer RealContainer { get; init; } - public ContainerSizeScanContainer(IContainerScanSnapshotProvider provider) + internal SizeScanContainer() { _readOnlyExtensions = new ReadOnlyExtensionCollection(Extensions); IsLoading = _isLoading.AsObservable(); _size = new(childContainerSizes => Task.FromResult(childContainerSizes.Sum())); + Size = _size.Debounce(TimeSpan.FromSeconds(1)); CreatedAt = DateTime.Now; - Provider = provider; } public async Task AddSizeSourceAsync(IDeclarativeProperty sizeElement) @@ -71,4 +75,19 @@ public class ContainerSizeScanContainer : IContainerSizeScanContainer IsLoaded = true; return Task.CompletedTask; } + + public required IDeclarativeProperty Status { get; init; } = new DeclarativeProperty(); + + public required SizeScanTask SizeScanTask { get; init; } + + public Task HandleEsc() + { + if (!SizeScanTask.IsRunning) + { + return Task.FromResult(new ContainerEscapeResult(false)); + } + + SizeScanTask.Stop(); + return Task.FromResult(new ContainerEscapeResult(true)); + } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanElement.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanElement.cs new file mode 100644 index 0000000..da09993 --- /dev/null +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanElement.cs @@ -0,0 +1,32 @@ +using System.Collections.ObjectModel; +using DeclarativeProperty; +using FileTime.Core.ContentAccess; +using FileTime.Core.Enums; +using FileTime.Core.Models; +using FileTime.Core.Timeline; + +namespace FileTime.App.ContainerSizeScanner; + +public record SizeScanElement : ISizeScanElement +{ + public required string Name { get; init; } + public required string DisplayName { get; init; } + public required FullName FullName { get; init; } + public required NativePath NativePath { get; init; } + public AbsolutePath? Parent { get; init; } + + public required IDeclarativeProperty Size { get; init; } + public bool IsHidden => false; + public bool IsExists => true; + public DateTime? CreatedAt { get; } = DateTime.Now; + public SupportsDelete CanDelete => SupportsDelete.False; + public bool CanRename => false; + public string? Attributes => ""; + public required IContentProvider Provider { get; init; } + public PointInTime PointInTime { get; } = PointInTime.Present; + public ObservableCollection Exceptions { get; } = new(); + public ReadOnlyExtensionCollection Extensions { get; } = new(); + public AbsolutePathType Type => AbsolutePathType.Element; + + public IItem WithParent(AbsolutePath parent) => this with {Parent = parent}; +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanTask.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanTask.cs index df7a78b..193daa2 100644 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanTask.cs +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/SizeScanTask.cs @@ -9,13 +9,19 @@ namespace FileTime.App.ContainerSizeScanner; public class SizeScanTask : ISizeScanTask { + private bool _cancelled; + private int _processedItems; + private ulong _processedItemsTotal; private IContainer _scanSizeOf = null!; private readonly IContainerScanSnapshotProvider _containerScanSnapshotProvider; private readonly ITimelessContentProvider _timelessContentProvider; private readonly ILogger _logger; private Thread? _sizeScanThread; private static int _searchId = 1; - public IContainerSizeScanContainer SizeContainer { get; private set; } = null!; + private readonly DeclarativeProperty _containerStatus = new(); + private readonly IDeclarativeProperty _containerStatusDebounced; + public ISizeScanContainer SizeSizeScanContainer { get; private set; } = null!; + public bool IsRunning { get; private set; } public SizeScanTask( IContainerScanSnapshotProvider containerScanSnapshotProvider, @@ -25,21 +31,25 @@ public class SizeScanTask : ISizeScanTask _containerScanSnapshotProvider = containerScanSnapshotProvider; _timelessContentProvider = timelessContentProvider; _logger = logger; + _containerStatusDebounced = _containerStatus.Debounce(TimeSpan.FromMilliseconds(250)); } public void Init(IContainer scanSizeOf) { _scanSizeOf = scanSizeOf; var name = $"{_searchId++}_{scanSizeOf.Name}"; - var randomId = ContainerScanSnapshotProvider.ContentProviderName + Constants.SeparatorChar + name; - SizeContainer = new ContainerSizeScanContainer(_containerScanSnapshotProvider) + var randomId = ContainerScanProvider.ContentProviderName + Constants.SeparatorChar + name; + SizeSizeScanContainer = new SizeScanContainer { Name = name, DisplayName = scanSizeOf.DisplayName, FullName = new FullName(randomId), NativePath = new NativePath(randomId), Parent = new AbsolutePath(_timelessContentProvider, _containerScanSnapshotProvider), - RealContainer = scanSizeOf + RealContainer = scanSizeOf, + Provider = _containerScanSnapshotProvider, + Status = _containerStatusDebounced, + SizeScanTask = this }; } @@ -52,23 +62,34 @@ public class SizeScanTask : ISizeScanTask _sizeScanThread = sizeScanThread; } + public void Stop() => _cancelled = true; + private async void Run() { try { - await TraverseTree(_scanSizeOf, SizeContainer); + IsRunning = true; + await SizeSizeScanContainer.StartLoadingAsync(); + await TraverseTree(_scanSizeOf, SizeSizeScanContainer); } catch (Exception ex) { _logger.LogError(ex, "Error while scanning container {ContainerName}", _scanSizeOf.Name); + SizeSizeScanContainer.Exceptions.Add(ex); } + + IsRunning = false; + await SizeSizeScanContainer.StopLoadingAsync(); } //TODO: make static private async Task TraverseTree( IContainer realContainer, - IContainerSizeScanContainer container) + ISizeScanContainer sizeScanContainer) { + if(_cancelled) return; + + await realContainer.WaitForLoaded(); var resolvedItems = new List(realContainer.Items.Count); foreach (var item in realContainer.Items) { @@ -78,34 +99,59 @@ public class SizeScanTask : ISizeScanTask foreach (var element in resolvedItems.OfType()) { + if(_cancelled) return; + var fileExtension = element.GetExtension(); if (fileExtension?.Size is not { } size) continue; - var childName = container.FullName!.GetChild(element.Name).Path; - await container.AddSizeSourceAsync(new DeclarativeProperty(size)); - container.Items.Add(new AbsolutePath( + var sizeProperty = new DeclarativeProperty(size); + + var childName = sizeScanContainer.FullName!.GetChild(element.Name).Path; + await sizeScanContainer.AddSizeSourceAsync(sizeProperty); + sizeScanContainer.Items.Add(new AbsolutePath( _timelessContentProvider, PointInTime.Present, new FullName(childName), AbsolutePathType.Element)); + + + var childSearchContainer = new SizeScanElement + { + Name = element.Name, + DisplayName = element.DisplayName, + FullName = new FullName(childName), + NativePath = new NativePath(childName), + Parent = new AbsolutePath(_timelessContentProvider, sizeScanContainer), + Provider = _containerScanSnapshotProvider, + Size = sizeProperty + }; + sizeScanContainer.SizeItems.Add(childSearchContainer); + _processedItems++; + _processedItemsTotal++; } foreach (var childContainer in resolvedItems.OfType()) { - var childName = container.FullName!.GetChild(childContainer.Name).Path; - var childSearchContainer = new ContainerSizeScanContainer(_containerScanSnapshotProvider) + if(_cancelled) return; + + var childName = sizeScanContainer.FullName!.GetChild(childContainer.Name).Path; + var childSearchContainer = new SizeScanContainer { Name = childContainer.Name, DisplayName = childContainer.DisplayName, FullName = new FullName(childName), NativePath = new NativePath(childName), - Parent = new AbsolutePath(_timelessContentProvider, container), - RealContainer = childContainer + Parent = new AbsolutePath(_timelessContentProvider, sizeScanContainer), + RealContainer = childContainer, + Provider = _containerScanSnapshotProvider, + Status = _containerStatusDebounced, + SizeScanTask = this }; - container.ChildContainers.Add(childSearchContainer); - await container.AddSizeSourceAsync(childSearchContainer.Size); - container.Items.Add(new AbsolutePath( + sizeScanContainer.ChildContainers.Add(childSearchContainer); + sizeScanContainer.SizeItems.Add(childSearchContainer); + await sizeScanContainer.AddSizeSourceAsync(childSearchContainer.Size); + sizeScanContainer.Items.Add(new AbsolutePath( _timelessContentProvider, PointInTime.Present, new FullName(childName), @@ -113,5 +159,16 @@ public class SizeScanTask : ISizeScanTask await TraverseTree(childContainer, childSearchContainer); } + + _processedItems++; + _processedItemsTotal++; + await _containerStatus.SetValue("Items processed: " + _processedItemsTotal); + + if (_processedItems > 1000) + { + _processedItems = 0; + //Let some time for the UI to refresh + await Task.Delay(1000); + } } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.ContainerSizeScanner/Startup.cs b/src/AppCommon/FileTime.App.ContainerSizeScanner/Startup.cs index c967b08..a18e927 100644 --- a/src/AppCommon/FileTime.App.ContainerSizeScanner/Startup.cs +++ b/src/AppCommon/FileTime.App.ContainerSizeScanner/Startup.cs @@ -9,10 +9,10 @@ public static class Startup { public static IServiceCollection AddContainerSizeScanner(this IServiceCollection services) { - services.TryAddSingleton(); + services.TryAddSingleton(); services.AddSingleton(sp => sp.GetRequiredService()); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); return services; } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Interactions/DoubleTextPreview.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Interactions/DoubleTextPreview.cs index 316683b..54a6531 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Interactions/DoubleTextPreview.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Interactions/DoubleTextPreview.cs @@ -10,6 +10,6 @@ public class DoubleTextPreview : IPreviewElement public IObservable> Text1 { get; init; } = new BehaviorSubject>(new()); public IObservable> Text2 { get; init; } = new BehaviorSubject>(new()); - public PreviewType PreviewType { get; } = PreviewType.DoubleTextList; + public PreviewType PreviewType => PreviewType.DoubleTextList; object IPreviewElement.PreviewType => PreviewType; } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Models/Enums/ItemAttributeType.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Models/Enums/ItemAttributeType.cs deleted file mode 100644 index 09e9074..0000000 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Models/Enums/ItemAttributeType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace FileTime.App.Core.Models.Enums; - -public enum ItemAttributeType -{ - File, - Element, - Container, - SizeContainer -} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Models/Traits/ISizeProvider.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Models/Traits/ISizeProvider.cs new file mode 100644 index 0000000..f4609f2 --- /dev/null +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Models/Traits/ISizeProvider.cs @@ -0,0 +1,8 @@ +using DeclarativeProperty; + +namespace FileTime.App.Core.Models.Traits; + +public interface ISizeProvider +{ + IDeclarativeProperty Size { get; } +} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IItemPreviewService.cs b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IItemPreviewService.cs index 597e2ea..f016108 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/Services/IItemPreviewService.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/Services/IItemPreviewService.cs @@ -1,8 +1,9 @@ +using DeclarativeProperty; using FileTime.App.Core.ViewModels.ItemPreview; namespace FileTime.App.Core.Services; public interface IItemPreviewService { - IObservable ItemPreview { get; } + IDeclarativeProperty ItemPreview { get; } } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs index deef171..9af5822 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IAppState.cs @@ -13,6 +13,7 @@ public interface IAppState IDeclarativeProperty ViewMode { get; } DeclarativeProperty RapidTravelText { get; } ITimelineViewModel TimelineViewModel { get; } + IDeclarativeProperty ContainerStatus { get; } void AddTab(ITabViewModel tabViewModel); void RemoveTab(ITabViewModel tabViewModel); diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerSizeContainerViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerSizeContainerViewModel.cs deleted file mode 100644 index fef5745..0000000 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IContainerSizeContainerViewModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -using FileTime.App.Core.Models.Enums; -using FileTime.Core.Models; -using InitableService; - -namespace FileTime.App.Core.ViewModels; - -public interface IContainerSizeContainerViewModel : IItemViewModel, IInitable -{ - long Size { get; set; } -} \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IElementViewModel.cs b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IElementViewModel.cs index 1350e76..41735c5 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IElementViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IElementViewModel.cs @@ -1,11 +1,14 @@ using FileTime.App.Core.Models.Enums; +using FileTime.App.Core.Models.Traits; using FileTime.Core.Models; using InitableService; namespace FileTime.App.Core.ViewModels; -public interface IElementViewModel : IItemViewModel, IInitable +public interface IElementViewModel : + IItemViewModel, + ISizeProvider, + IInitable { IElement? Element { get; } - long? Size { get; set; } } \ 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 acfc6e2..65fdd65 100644 --- a/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IItemViewModel.cs +++ b/src/AppCommon/FileTime.App.Core.Abstraction/ViewModels/IItemViewModel.cs @@ -13,7 +13,7 @@ public interface IItemViewModel : IInitable IsSelected { get; set; } IDeclarativeProperty? IsMarked { get; set; } IDeclarativeProperty IsAlternative { get; } - IObservable ViewMode { get; set; } + IDeclarativeProperty ViewMode { get; set; } DateTime? CreatedAt { get; set; } string? Attributes { get; set; } bool EqualsTo(IItemViewModel? itemViewModel); diff --git a/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs b/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs index c4a208f..4036307 100644 --- a/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/ItemPreviewService.cs @@ -1,4 +1,5 @@ using System.Reactive.Linq; +using DeclarativeProperty; using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels.ItemPreview; using FileTime.Core.Models; @@ -10,7 +11,7 @@ public class ItemPreviewService : IItemPreviewService { private readonly IServiceProvider _serviceProvider; private readonly IEnumerable _itemPreviewProviders; - public IObservable ItemPreview { get; } + public IDeclarativeProperty ItemPreview { get; } public ItemPreviewService( IAppState appState, @@ -21,18 +22,15 @@ public class ItemPreviewService : IItemPreviewService _itemPreviewProviders = itemPreviewProviders; ItemPreview = appState .SelectedTab - .Select(t => - t?.CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250)) - ?? Observable.Return(null)) + .Map(t => t.CurrentSelectedItem) .Switch() - .Select(item => + .Debounce(TimeSpan.FromMilliseconds(250)) + .Map(async (item, _) => item == null - ? Observable.Return(null) - : Observable.FromAsync(async () => await Map(item)) + ? null + : await Map(item) ) - .Switch() - .Publish(null) - .RefCount(); + .DistinctUntilChanged(); } private async Task Map(IItemViewModel itemViewModel) diff --git a/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs b/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs index 55c7b55..afaf409 100644 --- a/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/Persistence/TabPersistenceService.cs @@ -112,6 +112,7 @@ public class TabPersistenceService : ITabPersistenceService try { if (tab.Path == null) continue; + if (_contentProvidersNotToRestore.Any(p => tab.Path.StartsWith(p))) continue; IContainer? container = null; var path = FullName.CreateSafe(tab.Path); @@ -186,7 +187,7 @@ public class TabPersistenceService : ITabPersistenceService public void SaveStates(CancellationToken token = default) { var state = new PersistenceRoot(SerializeTabStates()); - + var settingsDirectory = new DirectoryInfo(string.Join(Path.DirectorySeparatorChar, _settingsPath.Split(Path.DirectorySeparatorChar)[0..^1])); if (!settingsDirectory.Exists) settingsDirectory.Create(); diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs index 610dbc4..154409f 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/NavigationUserCommandHandlerService.cs @@ -332,7 +332,11 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase } else if (tabViewModel == null) { - var tab = await _serviceProvider.GetAsyncInitableResolver(_currentLocation?.Value ?? _localContentProvider) + var newLocation = _currentLocation?.Value?.FullName is { } fullName + ? (IContainer) await _timelessContentProvider.GetItemByFullNameAsync(fullName, PointInTime.Present) + : _localContentProvider; + + var tab = await _serviceProvider.GetAsyncInitableResolver(newLocation) .GetRequiredServiceAsync(); var newTabViewModel = _serviceProvider.GetInitableResolver(tab, number).GetRequiredService(); diff --git a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs index 3535626..c5249be 100644 --- a/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs +++ b/src/AppCommon/FileTime.App.Core/Services/UserCommandHandler/ToolUserCommandHandlerService.cs @@ -66,7 +66,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase if (_currentLocation?.Value is null) return; var searchTask = _containerScanSnapshotProvider.StartSizeScan(_currentLocation.Value); - var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, searchTask.SizeContainer)); + var openContainerCommand = new OpenContainerCommand(new AbsolutePath(_timelessContentProvider, searchTask.SizeSizeScanContainer)); await _userCommandHandlerService.HandleCommandAsync(openContainerCommand); } diff --git a/src/AppCommon/FileTime.App.Core/Startup.cs b/src/AppCommon/FileTime.App.Core/Startup.cs index 95dee30..97a8095 100644 --- a/src/AppCommon/FileTime.App.Core/Startup.cs +++ b/src/AppCommon/FileTime.App.Core/Startup.cs @@ -18,7 +18,6 @@ public static class Startup serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); - serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); serviceCollection.TryAddTransient(); serviceCollection.TryAddSingleton(); diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs b/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs index 81f1f28..e9dfbf6 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/AppStateBase.cs @@ -5,6 +5,8 @@ using DeclarativeProperty; using DynamicData; using FileTime.App.Core.Models.Enums; using FileTime.App.Core.ViewModels.Timeline; +using FileTime.Core.Models; +using FileTime.Core.Models.ContainerTraits; using MvvmGen; using MoreLinq; @@ -27,6 +29,8 @@ public abstract partial class AppStateBase : IAppState public IDeclarativeProperty SelectedTab { get; private set; } public DeclarativeProperty RapidTravelText { get; private set; } + public IDeclarativeProperty ContainerStatus { get; private set; } + partial void OnInitialize() { RapidTravelText = new(""); @@ -40,6 +44,12 @@ public abstract partial class AppStateBase : IAppState ); Tabs = new ReadOnlyObservableCollection(_tabs); + + ContainerStatus = SelectedTab + .Map(t => t?.CurrentLocation) + .Switch() + .Map(c => c is IStatusProviderContainer statusProvider ? statusProvider.Status : null) + .Switch(); } public void AddTab(ITabViewModel tabViewModel) diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ContainerSizeContainerViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ContainerSizeContainerViewModel.cs deleted file mode 100644 index ecbec6f..0000000 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ContainerSizeContainerViewModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -using FileTime.App.Core.Models.Enums; -using FileTime.App.Core.Services; -using FileTime.Core.Models; -using MvvmGen; - -namespace FileTime.App.Core.ViewModels; - -[ViewModel(GenerateConstructor = false)] -public partial class ContainerSizeContainerViewModel : ItemViewModel, IContainerSizeContainerViewModel -{ - [Property] - private long _size; - - public ContainerSizeContainerViewModel(IItemNameConverterService itemNameConverterService, IAppState appState) : base(itemNameConverterService, appState) - { - } - - 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/ElementViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ElementViewModel.cs index a37852c..73b6577 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ElementViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ElementViewModel.cs @@ -1,3 +1,4 @@ +using DeclarativeProperty; using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Services; using FileTime.Core.Models; @@ -9,9 +10,6 @@ namespace FileTime.App.Core.ViewModels; public partial class ElementViewModel : ItemViewModel, IElementViewModel { public IElement? Element => BaseItem as Element; - - [Property] - private long? _size; public ElementViewModel(IItemNameConverterService itemNameConverterService, IAppState appState) : base(itemNameConverterService, appState) { @@ -19,4 +17,6 @@ public partial class ElementViewModel : ItemViewModel, IElementViewModel public void Init(IElement item, ITabViewModel parentTab, ItemViewModelType itemViewModelType) => Init((IItem)item, parentTab, itemViewModelType); + + public IDeclarativeProperty Size { get; protected set; } = new DeclarativeProperty(0); } \ No newline at end of file diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs index 172c029..1350b42 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/FileViewModel.cs @@ -1,3 +1,4 @@ +using DeclarativeProperty; using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Services; using FileTime.Core.Models; @@ -15,7 +16,7 @@ public partial class FileViewModel : ElementViewModel, IFileViewModel public void Init(IElement item, FileExtension fileExtension, ITabViewModel parentTab, ItemViewModelType itemViewModelType) { - Init((IElement)item, parentTab, itemViewModelType); - Size = fileExtension.Size; + Init(item, parentTab, itemViewModelType); + Size = new DeclarativeProperty(fileExtension.Size ?? 0); } } \ 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 5632c4a..1e095f4 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ItemViewModel.cs @@ -1,3 +1,4 @@ +using System.Collections.ObjectModel; using System.ComponentModel; using System.Reactive.Linq; using DeclarativeProperty; @@ -27,7 +28,7 @@ public abstract partial class ItemViewModel : IItemViewModel [Property] private IDeclarativeProperty? _isMarked; - [Property] private IObservable _viewMode; + [Property] private IDeclarativeProperty _viewMode; [Property] private DateTime? _createdAt; @@ -38,8 +39,8 @@ public abstract partial class ItemViewModel : IItemViewModel public IDeclarativeProperty>? DisplayName { get; private set; } public void Init( - IItem item, - ITabViewModel parentTab, + IItem item, + ITabViewModel parentTab, ItemViewModelType itemViewModelType) { _parentTab = parentTab; @@ -55,7 +56,7 @@ public abstract partial class ItemViewModel : IItemViewModel var displayName = itemViewModelType switch { ItemViewModelType.Main => _appState.RapidTravelText.Map(async (s, _) => - _appState.ViewMode.Value != Models.Enums.ViewMode.RapidTravel + _appState.ViewMode.Value != Models.Enums.ViewMode.RapidTravel && _appState.SelectedTab.Value?.CurrentLocation.Value?.Provider is IItemNameConverterProvider nameConverterProvider ? (IReadOnlyList) await nameConverterProvider.GetItemNamePartsAsync(item) : _itemNameConverterService.GetDisplayName(item.DisplayName, s) @@ -75,15 +76,23 @@ public abstract partial class ItemViewModel : IItemViewModel ? parentTab.CurrentSelectedItem.Map(EqualsTo) : new DeclarativeProperty(IsInDeepestPath()); - IsAlternative = sourceCollection.Map(c => c?.Index().FirstOrDefault(i => EqualsTo(i.Value)).Key % 2 == 0); + IsAlternative = sourceCollection + .Debounce(TimeSpan.FromMilliseconds(100)) + .Map(c => + c?.Index().FirstOrDefault(i => EqualsTo(i.Value)).Key % 2 == 1 + ); - ViewMode = Observable.CombineLatest(IsMarked, IsSelected, IsAlternative, GenerateViewMode).Throttle(TimeSpan.FromMilliseconds(10)); + ViewMode = DeclarativePropertyHelpers + .CombineLatest(IsMarked, IsSelected, IsAlternative, GenerateViewMode) + .DistinctUntilChanged() + .Debounce(TimeSpan.FromMilliseconds(100)); Attributes = item.Attributes; CreatedAt = item.CreatedAt; } - private ItemViewMode GenerateViewMode(bool isMarked, bool isSelected, bool isAlternative) - => (isMarked, isSelected, isAlternative) switch + private Task GenerateViewMode(bool isMarked, bool isSelected, bool isAlternative) + { + var result = (isMarked, isSelected, isAlternative) switch { (true, true, _) => ItemViewMode.MarkedSelected, (true, false, true) => ItemViewMode.MarkedAlternative, @@ -93,12 +102,13 @@ public abstract partial class ItemViewModel : IItemViewModel _ => ItemViewMode.Default }; - - public bool EqualsTo(IItemViewModel? itemViewModel) - { - return BaseItem?.FullName?.Path is string path && path == itemViewModel?.BaseItem?.FullName?.Path; + return Task.FromResult(result); } + + public bool EqualsTo(IItemViewModel? itemViewModel) + => BaseItem?.FullName?.Path is { } path && path == itemViewModel?.BaseItem?.FullName?.Path; + private bool IsInDeepestPath() { if (_parentTab?.Tab?.LastDeepestSelectedPath is null diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs index 0dc98fe..5bdc45f 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/TabViewModel.cs @@ -162,7 +162,7 @@ public partial class TabViewModel : ITabViewModel .Selecting(i => MapItem(i)) .Ordering(i => i.Type) .ThenOrdering(i => i.Name) - .Selecting(i => MapItemToViewModel(i, ItemViewModelType.SelectedChild)); + .Selecting(i => MapItemToViewModel(i, ItemViewModelType.Parent)); return items; }); diff --git a/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationService.cs b/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationService.cs index 0536558..c93b9a0 100644 --- a/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationService.cs +++ b/src/AppCommon/FileTime.App.FrequencyNavigation/Services/FrequencyNavigationService.cs @@ -140,7 +140,7 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I { return _containerScores .Where(c => c.Key.Contains(searchText, StringComparison.OrdinalIgnoreCase)) - .OrderBy(c => GetWeightedScore(c.Value.Score, c.Value.LastAccessed)) + .OrderByDescending(c => GetWeightedScore(c.Value.Score, c.Value.LastAccessed)) .Select(c => c.Key) .ToList(); } diff --git a/src/Core/FileTime.Core.Abstraction/Models/ContainerEscapeResult.cs b/src/Core/FileTime.Core.Abstraction/Models/ContainerEscapeResult.cs new file mode 100644 index 0000000..d87293d --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Models/ContainerEscapeResult.cs @@ -0,0 +1,17 @@ +namespace FileTime.Core.Models; + +public class ContainerEscapeResult +{ + public bool Handled { get; } + public IContainer? NavigateTo { get; } + + public ContainerEscapeResult(bool handled) + { + Handled = handled; + } + + public ContainerEscapeResult(IContainer navigateTo) + { + NavigateTo = navigateTo; + } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/ContainerTraits/IEscHandlerContainer.cs b/src/Core/FileTime.Core.Abstraction/Models/ContainerTraits/IEscHandlerContainer.cs new file mode 100644 index 0000000..7fea2cf --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Models/ContainerTraits/IEscHandlerContainer.cs @@ -0,0 +1,6 @@ +namespace FileTime.Core.Models.ContainerTraits; + +public interface IEscHandlerContainer +{ + Task HandleEsc(); +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/ContainerTraits/IStatusProviderContainer.cs b/src/Core/FileTime.Core.Abstraction/Models/ContainerTraits/IStatusProviderContainer.cs new file mode 100644 index 0000000..4f1f1e5 --- /dev/null +++ b/src/Core/FileTime.Core.Abstraction/Models/ContainerTraits/IStatusProviderContainer.cs @@ -0,0 +1,8 @@ +using DeclarativeProperty; + +namespace FileTime.Core.Models.ContainerTraits; + +public interface IStatusProviderContainer +{ + IDeclarativeProperty Status { get; } +} \ No newline at end of file diff --git a/src/Core/FileTime.Core.Abstraction/Models/IItem.cs b/src/Core/FileTime.Core.Abstraction/Models/IItem.cs index 6ac211a..e9b5dd5 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/IItem.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/IItem.cs @@ -1,5 +1,4 @@ using System.Collections.ObjectModel; -using DynamicData; using FileTime.Core.ContentAccess; using FileTime.Core.Enums; using FileTime.Core.Timeline; diff --git a/src/Core/FileTime.Core.Abstraction/Models/ReadOnlyExtensionCollection.cs b/src/Core/FileTime.Core.Abstraction/Models/ReadOnlyExtensionCollection.cs index af73997..3c81d17 100644 --- a/src/Core/FileTime.Core.Abstraction/Models/ReadOnlyExtensionCollection.cs +++ b/src/Core/FileTime.Core.Abstraction/Models/ReadOnlyExtensionCollection.cs @@ -6,6 +6,11 @@ public class ReadOnlyExtensionCollection : IEnumerable { private readonly ExtensionCollection _collection; + public ReadOnlyExtensionCollection() + { + _collection = new ExtensionCollection(); + } + public ReadOnlyExtensionCollection(ExtensionCollection collection) { _collection = collection; diff --git a/src/Core/FileTime.Core.Models/Container.cs b/src/Core/FileTime.Core.Models/Container.cs index 465ffc6..ba64e3d 100644 --- a/src/Core/FileTime.Core.Models/Container.cs +++ b/src/Core/FileTime.Core.Models/Container.cs @@ -1,7 +1,6 @@ using System.Collections.ObjectModel; using System.Reactive.Linq; using System.Reactive.Subjects; -using DynamicData; using FileTime.Core.ContentAccess; using FileTime.Core.Enums; using FileTime.Core.Timeline; diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/DebugConverter.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/DebugConverter.cs new file mode 100644 index 0000000..7e93b01 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/DebugConverter.cs @@ -0,0 +1,11 @@ +using System.Globalization; +using Avalonia.Data.Converters; + +namespace FileTime.GuiApp.Converters; + +public class DebugConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) => value; + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => value; +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/FormatSizeConverter.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/FormatSizeConverter.cs index 282e126..1d41969 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/FormatSizeConverter.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/FormatSizeConverter.cs @@ -1,46 +1,29 @@ using System.Globalization; using Avalonia.Data.Converters; +using ByteSizeLib; namespace FileTime.GuiApp.Converters; public class FormatSizeConverter : IValueConverter { - private const long OneKiloByte = 1024; - private const long OneMegaByte = OneKiloByte * 1024; - private const long OneGigaByte = OneMegaByte * 1024; - private const long OneTerraByte = OneGigaByte * 1024; - - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - return (value, int.TryParse(parameter?.ToString(), out var prec)) switch + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) => + (value, int.TryParse(parameter?.ToString(), out var prec)) switch { (long size, true) => ToSizeString(size, prec), (long size, false) => ToSizeString(size), (null, _) => "...", _ => value }; - } public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) { throw new NotImplementedException(); } - public static string ToSizeString(long fileSize, int precision = 1) + public static string ToSizeString(long fileSize, int? precision = null) { - var fileSizeD = (decimal)fileSize; - var (size, suffix) = fileSize switch - { - > OneTerraByte => (fileSizeD / OneTerraByte, "T"), - > OneGigaByte => (fileSizeD / OneGigaByte, "G"), - > OneMegaByte => (fileSizeD / OneMegaByte, "M"), - > OneKiloByte => (fileSizeD / OneKiloByte, "K"), - _ => (fileSizeD, "B") - }; - - var result = string.Format("{0:N" + precision + "}", size).Replace(',', '.'); - - if (result.Contains('.')) result = result.TrimEnd('0').TrimEnd('.'); - return result + " " + suffix; + var size = new ByteSize(fileSize); + return precision == null? size.ToString() + : size.ToString("0." + new string('#', precision.Value)); } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/IsTypeConverter.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/IsTypeConverter.cs new file mode 100644 index 0000000..21f21d0 --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/IsTypeConverter.cs @@ -0,0 +1,25 @@ +using System.Globalization; +using Avalonia.Data.Converters; + +namespace FileTime.GuiApp.Converters; + +public class IsTypeConverter : IValueConverter +{ + public bool Invert { get; set; } + + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (parameter is Type parameterType) + { + var result = parameterType.IsInstanceOfType(value); + + if(Invert) result = !result; + return result; + } + + throw new NotSupportedException(); + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + => throw new NotImplementedException(); +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemSizeToBrushConverter.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemSizeToBrushConverter.cs new file mode 100644 index 0000000..d1b5bbe --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemSizeToBrushConverter.cs @@ -0,0 +1,41 @@ +using System.Globalization; +using Avalonia.Data.Converters; +using Avalonia.Media; +using Avalonia.Threading; +using FileTime.App.ContainerSizeScanner; +using FileTime.GuiApp.Helper; + +namespace FileTime.GuiApp.Converters; + +public class ItemSizeToBrushConverter : IMultiValueConverter +{ + public double HueDiff { get; set; } + public double Lightness { get; set; } = 0.75; + + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values is [ISizePreviewItem previewItem, ContainerPreview sizeContainerViewModel]) + { + var items = sizeContainerViewModel.TopItems; + var i = 0; + for (; i < items.Count; i++) + { + if (items[i].Name == previewItem.Name) break; + } + + var hue = (360d * i / (items.Count < 1 ? 1 : items.Count)) + HueDiff; + if (hue > 360) hue -= 360; + if (hue < 0) hue += 360; + + var (r, g, b) = ColorHelper.HlsToRgb(hue, Lightness, 1); + + var task = Dispatcher.UIThread.InvokeAsync(() => new SolidColorBrush(Color.FromRgb(r, g, b))); + task.Wait(); + return task.Result; + } + + return null; + } + + private static byte Normalize(byte b) => (byte) (255 - (255 - b) / 2); +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemSizeToSizeConverter.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemSizeToSizeConverter.cs new file mode 100644 index 0000000..461ec3a --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemSizeToSizeConverter.cs @@ -0,0 +1,19 @@ +using System.Globalization; +using Avalonia.Data.Converters; +using FileTime.App.ContainerSizeScanner; + +namespace FileTime.GuiApp.Converters; + +public class ItemSizeToSizeConverter : IMultiValueConverter +{ + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values is [ISizePreviewItem previewItem, ContainerPreview sizeContainerViewModel, double width and > 0]) + { + var cumulativeSize = sizeContainerViewModel.TopItems.Select(i => i.Size.Value).Sum(); + return width * previewItem.Size.Value / cumulativeSize; + } + + return null; + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemViewModelIsAttributeTypeConverter.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemViewModelIsAttributeTypeConverter.cs deleted file mode 100644 index 62bc9f6..0000000 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ItemViewModelIsAttributeTypeConverter.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Globalization; -using Avalonia.Data.Converters; -using FileTime.App.Core.Models.Enums; -using FileTime.App.Core.ViewModels; - -namespace FileTime.GuiApp.Converters; - -public class ItemViewModelIsAttributeTypeConverter : IValueConverter -{ - public bool Invert { get; set; } - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - var attributeType = GetAttributeType(value); - if (parameter == null) return attributeType; - var result = parameter is ItemAttributeType targetAttribute && attributeType == targetAttribute; - if (Invert && parameter is ItemAttributeType) result = !result; - return result; - } - - private static ItemAttributeType? GetAttributeType(object? value) - { - return value switch - { - IFileViewModel => ItemAttributeType.File, - IContainerSizeContainerViewModel => ItemAttributeType.SizeContainer, - IElementViewModel => ItemAttributeType.Element, - IContainerViewModel => ItemAttributeType.Container, - _ => null - }; - } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } -} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Helper/ColorHelper.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Helper/ColorHelper.cs new file mode 100644 index 0000000..2ce5e3a --- /dev/null +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Helper/ColorHelper.cs @@ -0,0 +1,43 @@ +namespace FileTime.GuiApp.Helper; + +public static class ColorHelper +{ + // Convert an HLS value into an RGB value. + public static (byte r, byte g, byte b) HlsToRgb(double h, double l, double s) + { + double p2; + if (l <= 0.5) p2 = l * (1 + s); + else p2 = l + s - l * s; + + var p1 = 2 * l - p2; + double doubleR, doubleG, doubleB; + if (s == 0) + { + doubleR = l; + doubleG = l; + doubleB = l; + } + else + { + doubleR = QqhToRgb(p1, p2, h + 120); + doubleG = QqhToRgb(p1, p2, h); + doubleB = QqhToRgb(p1, p2, h - 120); + } + + // Convert RGB to the 0 to 255 range. + return ((byte) (doubleR * 255.0), + (byte) (doubleG * 255.0), + (byte) (doubleB * 255.0)); + } + + private static double QqhToRgb(double q1, double q2, double hue) + { + if (hue > 360) hue -= 360; + else if (hue < 0) hue += 360; + + if (hue < 60) return q1 + (q2 - q1) * hue / 60; + if (hue < 180) return q2; + if (hue < 240) return q1 + (q2 - q1) * (240 - hue) / 60; + return q1; + } +} \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Converters.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Converters.axaml index d17ba6c..94ad8b5 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Converters.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Resources/Converters.axaml @@ -26,18 +26,13 @@ SelectedBrush="{StaticResource SelectedItemBackgroundBrush}" x:Key="ItemViewModeToBackgroundConverter" /> - - - - + + @@ -47,6 +42,16 @@ x:Key="PathPreformatter" /> + + + + + + + \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/DefaultModeKeyInputHandler.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/DefaultModeKeyInputHandler.cs index f597edf..9f94176 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/DefaultModeKeyInputHandler.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/DefaultModeKeyInputHandler.cs @@ -5,6 +5,7 @@ using FileTime.App.Core.UserCommand; using FileTime.App.Core.ViewModels; using FileTime.Core.Extensions; using FileTime.Core.Models; +using FileTime.Core.Models.ContainerTraits; using FileTime.GuiApp.Configuration; using FileTime.GuiApp.Extensions; using FileTime.GuiApp.Models; @@ -66,20 +67,23 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler if (key == Key.Escape) { - bool doGeneralReset = _appState.PreviousKeys.Count > 1 || _appState.IsAllShortcutVisible; + var doGeneralReset = _appState.PreviousKeys.Count > 1 || _appState.IsAllShortcutVisible; if ((_openModals.Collection?.Count ?? 0) > 0) { _modalService.CloseModal(_openModals.Collection!.Last()); } - /*else if (_currentLocation.Container.CanHandleEscape) + else if (_currentLocation is IEscHandlerContainer escHandler) { - var escapeResult = await _currentLocation.Container.HandleEscape(); + var escapeResult = await escHandler.HandleEsc(); if (escapeResult.NavigateTo != null) { setHandled(true); _appState.PreviousKeys.Clear(); - await _appState.SelectedTab.OpenContainer(escapeResult.NavigateTo); + if (_appState.SelectedTab.Value?.Tab is { } selectedTab) + { + await selectedTab.SetCurrentLocation(escapeResult.NavigateTo); + } } else { @@ -92,7 +96,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler doGeneralReset = true; } } - }*/ + } if (doGeneralReset) { diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/ItemView.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/ItemView.axaml index 20ff9f6..41c252b 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/ItemView.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/ItemView.axaml @@ -1,52 +1,49 @@ + xmlns:traits="clr-namespace:FileTime.App.Core.Models.Traits;assembly=FileTime.App.Core.Abstraction" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> + x:Name="RootGrid"> + Width="18" /> - - + + @@ -58,96 +55,55 @@ - + - - + + + + + + + + + + Text="{Binding CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}" + TextAlignment="Right" + Width="95" /> + Text="{Binding CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}" + TextAlignment="Right" + Width="35" /> - - - - + Text="{Binding BaseItem.Attributes}" + TextAlignment="Right" + Width="45" /> - - - - - + - - - - - - - \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index 65d3163..4e65630 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -61,8 +61,8 @@ - - + + @@ -437,7 +437,7 @@ AutoScrollToSelectedItem="True" Classes="ContentListView" IsTabStop="True" - ItemsSource="{Binding AppState.SelectedTab^.ParentsChildren.Value}" + ItemsSource="{Binding AppState.SelectedTab.Value.ParentsChildren.Value}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Visible"> @@ -459,7 +459,7 @@ Width="1" /> - + @@ -488,7 +488,7 @@ Foreground="{DynamicResource ErrorBrush}" Grid.Row="1" HorizontalAlignment="Center" - IsVisible="{Binding AppState.SelectedTab^.CurrentItems.Value.Count, Converter={StaticResource EqualsConverter}, ConverterParameter=0}" + IsVisible="{Binding AppState.SelectedTab.Value.CurrentItems.Value.Count, Converter={StaticResource EqualsConverter}, ConverterParameter=0}" Margin="10" x:Name="CurrentEmpty"> Empty @@ -504,11 +504,11 @@ Width="1" /> - - + + - + - + @@ -547,14 +547,14 @@ FontWeight="Bold" Foreground="{DynamicResource ErrorBrush}" HorizontalAlignment="Center" - IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren.Value.Count, Converter={StaticResource EqualsConverter}, ConverterParameter=0}" + IsVisible="{Binding AppState.SelectedTab.Value.SelectedsChildren.Value.Count, Converter={StaticResource EqualsConverter}, ConverterParameter=0}" Margin="10" x:Name="ChildEmpty"> Empty - + - + - - - - - - - - + + + + HorizontalAlignment="Center" + IsVisible="{Binding Mode, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Unknown}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}" + Text="Don't know how to preview this item." /> + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -684,7 +684,7 @@ @@ -718,57 +718,62 @@ - - - - - - - - - - - - - + + + + + - - - - - + - + - - - - - - - - + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Library/DeclarativeProperty/CombineLatestProperty.cs b/src/Library/DeclarativeProperty/CombineLatestProperty.cs index cbb06ef..a450209 100644 --- a/src/Library/DeclarativeProperty/CombineLatestProperty.cs +++ b/src/Library/DeclarativeProperty/CombineLatestProperty.cs @@ -37,4 +37,52 @@ public sealed class CombineLatestProperty : DeclarativeProperty var result = await _func(_value1, _value2); await SetNewValueAsync(result, cancellationToken); } +} + +public sealed class CombineLatestProperty : DeclarativePropertyBase +{ + private readonly Func> _func; + private T1? _value1; + private T2? _value2; + private T3? _value3; + + public CombineLatestProperty( + IDeclarativeProperty prop1, + IDeclarativeProperty prop2, + IDeclarativeProperty prop3, + Func> func, + Action? setValueHook = null) : base(setValueHook) + { + ArgumentNullException.ThrowIfNull(prop1); + ArgumentNullException.ThrowIfNull(prop2); + ArgumentNullException.ThrowIfNull(prop3); + + _func = func; + + _value1 = prop1.Value is null ? default : prop1.Value; + _value2 = prop2.Value is null ? default : prop2.Value; + _value3 = prop3.Value is null ? default : prop3.Value; + + prop1.Subscribe(async (value1, token) => + { + _value1 = value1; + await UpdateAsync(token); + }); + prop2.Subscribe(async (value2, token) => + { + _value2 = value2; + await UpdateAsync(token); + }); + prop3.Subscribe(async (value3, token) => + { + _value3 = value3; + await UpdateAsync(token); + }); + } + + private async Task UpdateAsync(CancellationToken cancellationToken = default) + { + var result = await _func(_value1, _value2, _value3); + await SetNewValueAsync(result, cancellationToken); + } } \ No newline at end of file diff --git a/src/Library/DeclarativeProperty/DeclarativeProperty.cs b/src/Library/DeclarativeProperty/DeclarativeProperty.cs index 8eab7ba..732a0a8 100644 --- a/src/Library/DeclarativeProperty/DeclarativeProperty.cs +++ b/src/Library/DeclarativeProperty/DeclarativeProperty.cs @@ -8,6 +8,14 @@ public static class DeclarativePropertyHelpers Func> func, Action? setValueHook = null) => new(prop1, prop2, func, setValueHook); + + public static CombineLatestProperty CombineLatest( + IDeclarativeProperty prop1, + IDeclarativeProperty prop2, + IDeclarativeProperty prop3, + Func> func, + Action? setValueHook = null) + => new(prop1, prop2, prop3, func, setValueHook); } public sealed class DeclarativeProperty : DeclarativePropertyBase