Container size scan, improvements

This commit is contained in:
2023-08-03 00:00:45 +02:00
parent 1713973c3a
commit f4d361f767
57 changed files with 814 additions and 532 deletions

View File

@@ -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<long> sizeElement);
ObservableCollection<IContainerSizeScanContainer> ChildContainers { get; }
IContainer RealContainer { get; init; }
IDeclarativeProperty<long> Size { get; }
}

View File

@@ -1,8 +1,8 @@
using FileTime.Core.Models; using FileTime.App.Core.Models.Traits;
using FileTime.Core.Models;
namespace FileTime.App.ContainerSizeScanner; namespace FileTime.App.ContainerSizeScanner;
public interface ISizeItem : IItem public interface ISizeItem : IItem, ISizeProvider
{ {
} }

View File

@@ -1,8 +1,10 @@
using FileTime.App.Core.ViewModels.ItemPreview; using DeclarativeProperty;
using FileTime.App.Core.ViewModels.ItemPreview;
namespace FileTime.App.ContainerSizeScanner; namespace FileTime.App.ContainerSizeScanner;
public interface ISizePreviewItem : IItemPreviewViewModel public interface ISizePreviewItem
{ {
IDeclarativeProperty<long> Size { get; }
string Name { get; }
} }

View File

@@ -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<long> sizeElement);
ObservableCollection<ISizeScanContainer> ChildContainers { get; }
ObservableCollection<ISizeScanElement> ChildElements { get; }
ObservableCollection<ISizeItem> SizeItems { get; }
IContainer RealContainer { get; init; }
Task StartLoadingAsync();
Task StopLoadingAsync();
}

View File

@@ -0,0 +1,8 @@
using FileTime.Core.Models;
namespace FileTime.App.ContainerSizeScanner;
public interface ISizeScanElement : ISizeItem, IElement
{
}

View File

@@ -5,6 +5,8 @@ namespace FileTime.App.ContainerSizeScanner;
public interface ISizeScanTask : IInitable<IContainer> public interface ISizeScanTask : IInitable<IContainer>
{ {
IContainerSizeScanContainer SizeContainer { get; } ISizeScanContainer SizeSizeScanContainer { get; }
bool IsRunning { get; }
void Start(); void Start();
void Stop();
} }

View File

@@ -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<long> Size { get; }
}

View File

@@ -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<long> Size { get; }
}

View File

@@ -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<ISizePreviewItem> 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();
}

View File

@@ -7,14 +7,14 @@ using InitableService;
namespace FileTime.App.ContainerSizeScanner; namespace FileTime.App.ContainerSizeScanner;
public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScanSnapshotProvider public class ContainerScanProvider : ContentProviderBase, IContainerScanSnapshotProvider
{ {
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly List<ISizeScanTask> _sizeScanTasks = new(); private readonly List<ISizeScanTask> _sizeScanTasks = new();
internal const string ContentProviderName = "container-size-scan"; internal const string ContentProviderName = "container-size-scan";
public ContainerScanSnapshotProvider( public ContainerScanProvider(
ITimelessContentProvider timelessContentProvider, ITimelessContentProvider timelessContentProvider,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
: base(ContentProviderName, timelessContentProvider) : base(ContentProviderName, timelessContentProvider)
@@ -36,7 +36,7 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan
var pathParts = fullName.Path.Split(Constants.SeparatorChar); 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) if (pathParts.Length == 2)
return item ?? throw new ItemNotFoundException(fullName); return item ?? throw new ItemNotFoundException(fullName);
@@ -49,10 +49,10 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan
if (item is not null) if (item is not null)
{ {
var container = item.ChildContainers.FirstOrDefault(c => c.Name == pathParts[^1]); var childItem = item.SizeItems.FirstOrDefault(c => c.Name == pathParts[^1]);
if (container is not null) return container; 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); if (childName is null) throw new ItemNotFoundException(fullName);
return await _timelessContentProvider.GetItemByFullNameAsync( return await _timelessContentProvider.GetItemByFullNameAsync(
@@ -61,7 +61,7 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan
forceResolve, forceResolve,
forceResolvePathType, forceResolvePathType,
itemInitializationSettings itemInitializationSettings
); );*/
} }
throw new ItemNotFoundException(fullName); throw new ItemNotFoundException(fullName);
@@ -106,7 +106,7 @@ public class ContainerScanSnapshotProvider : ContentProviderBase, IContainerScan
_sizeScanTasks.Add(searchTask); _sizeScanTasks.Add(searchTask);
searchTask.Start(); searchTask.Start();
Items.Add(new AbsolutePath(_timelessContentProvider, searchTask.SizeContainer)); Items.Add(new AbsolutePath(_timelessContentProvider, searchTask.SizeSizeScanContainer));
return searchTask; return searchTask;
} }

View File

@@ -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<ISizePreviewItem> Items { get; }
public ContainerSizeContainerPreview(IContainerSizeScanContainer container)
{
Items = container
.ChildContainers
.Ordering(c => c.Size)
.Taking(0, 10)
.Selecting();
}
}

View File

@@ -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<IItemPreviewViewModel> CreatePreviewAsync(IItem item)
{
if(item is not ContainerSizeScanContainer container) throw new NotSupportedException();
return Task.FromResult((IItemPreviewViewModel)new ContainerSizeContainerPreview(container));
}
}

View File

@@ -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<Exception> Exceptions,
ReadOnlyExtensionCollection Extensions) : IElement
{
public AbsolutePathType Type => AbsolutePathType.Element;
public IItem WithParent(AbsolutePath parent) => this with { Parent = parent };
}

View File

@@ -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<IItemPreviewViewModel> CreatePreviewAsync(IItem item)
{
if (item is not SizeScanContainer container) throw new NotSupportedException();
return Task.FromResult((IItemPreviewViewModel) new ContainerPreview(container));
}
}

View File

@@ -5,12 +5,13 @@ using DeclarativeProperty;
using FileTime.Core.ContentAccess; using FileTime.Core.ContentAccess;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Models.ContainerTraits;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;
namespace FileTime.App.ContainerSizeScanner; namespace FileTime.App.ContainerSizeScanner;
//TODO: create readonly version //TODO: create readonly version
public class ContainerSizeScanContainer : IContainerSizeScanContainer public record SizeScanContainer : ISizeScanContainer, IEscHandlerContainer
{ {
private readonly ReadOnlyExtensionCollection _readOnlyExtensions; private readonly ReadOnlyExtensionCollection _readOnlyExtensions;
private readonly BehaviorSubject<bool> _isLoading = new(false); private readonly BehaviorSubject<bool> _isLoading = new(false);
@@ -19,20 +20,20 @@ public class ContainerSizeScanContainer : IContainerSizeScanContainer
public required string DisplayName { get; init; } public required string DisplayName { get; init; }
public required FullName? FullName { get; init; } public required FullName? FullName { get; init; }
public required NativePath? NativePath { 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 IsHidden => false;
public bool IsExists => true; public bool IsExists => true;
public DateTime? CreatedAt { get; } public DateTime? CreatedAt { get; } = DateTime.Now;
public SupportsDelete CanDelete => SupportsDelete.True; public SupportsDelete CanDelete => SupportsDelete.True;
public bool CanRename => false; public bool CanRename => false;
public IContentProvider Provider { get; }
public string? Attributes => null; public string? Attributes => null;
public AbsolutePathType Type => AbsolutePathType.Container; public AbsolutePathType Type => AbsolutePathType.Container;
public PointInTime PointInTime => PointInTime.Present; public PointInTime PointInTime => PointInTime.Present;
public ObservableCollection<Exception> Exceptions { get; } = new(); public ObservableCollection<Exception> Exceptions { get; } = new();
public ExtensionCollection Extensions { get; } = new(); public ExtensionCollection Extensions { get; } = new();
ReadOnlyExtensionCollection IItem.Extensions => _readOnlyExtensions; ReadOnlyExtensionCollection IItem.Extensions => _readOnlyExtensions;
public IItem WithParent(AbsolutePath parent) => throw new NotImplementedException(); public IItem WithParent(AbsolutePath parent) => this with {Parent = parent};
public ObservableCollection<AbsolutePath> Items { get; } = new(); public ObservableCollection<AbsolutePath> Items { get; } = new();
public IObservable<bool> IsLoading { get; } public IObservable<bool> IsLoading { get; }
@@ -41,18 +42,21 @@ public class ContainerSizeScanContainer : IContainerSizeScanContainer
public bool AllowRecursiveDeletion => false; public bool AllowRecursiveDeletion => false;
public IDeclarativeProperty<long> Size => _size; public IDeclarativeProperty<long> Size { get; }
public ObservableCollection<IContainerSizeScanContainer> ChildContainers { get; } = new();
public ObservableCollection<ISizeScanContainer> ChildContainers { get; } = new();
public ObservableCollection<ISizeScanElement> ChildElements { get; } = new();
public ObservableCollection<ISizeItem> SizeItems { get; } = new();
public required IContainer RealContainer { get; init; } public required IContainer RealContainer { get; init; }
public ContainerSizeScanContainer(IContainerScanSnapshotProvider provider) internal SizeScanContainer()
{ {
_readOnlyExtensions = new ReadOnlyExtensionCollection(Extensions); _readOnlyExtensions = new ReadOnlyExtensionCollection(Extensions);
IsLoading = _isLoading.AsObservable(); IsLoading = _isLoading.AsObservable();
_size = new(childContainerSizes => Task.FromResult(childContainerSizes.Sum())); _size = new(childContainerSizes => Task.FromResult(childContainerSizes.Sum()));
Size = _size.Debounce(TimeSpan.FromSeconds(1));
CreatedAt = DateTime.Now; CreatedAt = DateTime.Now;
Provider = provider;
} }
public async Task AddSizeSourceAsync(IDeclarativeProperty<long> sizeElement) public async Task AddSizeSourceAsync(IDeclarativeProperty<long> sizeElement)
@@ -71,4 +75,19 @@ public class ContainerSizeScanContainer : IContainerSizeScanContainer
IsLoaded = true; IsLoaded = true;
return Task.CompletedTask; return Task.CompletedTask;
} }
public required IDeclarativeProperty<string> Status { get; init; } = new DeclarativeProperty<string>();
public required SizeScanTask SizeScanTask { get; init; }
public Task<ContainerEscapeResult> HandleEsc()
{
if (!SizeScanTask.IsRunning)
{
return Task.FromResult(new ContainerEscapeResult(false));
}
SizeScanTask.Stop();
return Task.FromResult(new ContainerEscapeResult(true));
}
} }

View File

@@ -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<long> 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<Exception> Exceptions { get; } = new();
public ReadOnlyExtensionCollection Extensions { get; } = new();
public AbsolutePathType Type => AbsolutePathType.Element;
public IItem WithParent(AbsolutePath parent) => this with {Parent = parent};
}

View File

@@ -9,13 +9,19 @@ namespace FileTime.App.ContainerSizeScanner;
public class SizeScanTask : ISizeScanTask public class SizeScanTask : ISizeScanTask
{ {
private bool _cancelled;
private int _processedItems;
private ulong _processedItemsTotal;
private IContainer _scanSizeOf = null!; private IContainer _scanSizeOf = null!;
private readonly IContainerScanSnapshotProvider _containerScanSnapshotProvider; private readonly IContainerScanSnapshotProvider _containerScanSnapshotProvider;
private readonly ITimelessContentProvider _timelessContentProvider; private readonly ITimelessContentProvider _timelessContentProvider;
private readonly ILogger<SizeScanTask> _logger; private readonly ILogger<SizeScanTask> _logger;
private Thread? _sizeScanThread; private Thread? _sizeScanThread;
private static int _searchId = 1; private static int _searchId = 1;
public IContainerSizeScanContainer SizeContainer { get; private set; } = null!; private readonly DeclarativeProperty<string> _containerStatus = new();
private readonly IDeclarativeProperty<string> _containerStatusDebounced;
public ISizeScanContainer SizeSizeScanContainer { get; private set; } = null!;
public bool IsRunning { get; private set; }
public SizeScanTask( public SizeScanTask(
IContainerScanSnapshotProvider containerScanSnapshotProvider, IContainerScanSnapshotProvider containerScanSnapshotProvider,
@@ -25,21 +31,25 @@ public class SizeScanTask : ISizeScanTask
_containerScanSnapshotProvider = containerScanSnapshotProvider; _containerScanSnapshotProvider = containerScanSnapshotProvider;
_timelessContentProvider = timelessContentProvider; _timelessContentProvider = timelessContentProvider;
_logger = logger; _logger = logger;
_containerStatusDebounced = _containerStatus.Debounce(TimeSpan.FromMilliseconds(250));
} }
public void Init(IContainer scanSizeOf) public void Init(IContainer scanSizeOf)
{ {
_scanSizeOf = scanSizeOf; _scanSizeOf = scanSizeOf;
var name = $"{_searchId++}_{scanSizeOf.Name}"; var name = $"{_searchId++}_{scanSizeOf.Name}";
var randomId = ContainerScanSnapshotProvider.ContentProviderName + Constants.SeparatorChar + name; var randomId = ContainerScanProvider.ContentProviderName + Constants.SeparatorChar + name;
SizeContainer = new ContainerSizeScanContainer(_containerScanSnapshotProvider) SizeSizeScanContainer = new SizeScanContainer
{ {
Name = name, Name = name,
DisplayName = scanSizeOf.DisplayName, DisplayName = scanSizeOf.DisplayName,
FullName = new FullName(randomId), FullName = new FullName(randomId),
NativePath = new NativePath(randomId), NativePath = new NativePath(randomId),
Parent = new AbsolutePath(_timelessContentProvider, _containerScanSnapshotProvider), 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; _sizeScanThread = sizeScanThread;
} }
public void Stop() => _cancelled = true;
private async void Run() private async void Run()
{ {
try try
{ {
await TraverseTree(_scanSizeOf, SizeContainer); IsRunning = true;
await SizeSizeScanContainer.StartLoadingAsync();
await TraverseTree(_scanSizeOf, SizeSizeScanContainer);
} }
catch (Exception ex) catch (Exception ex)
{ {
_logger.LogError(ex, "Error while scanning container {ContainerName}", _scanSizeOf.Name); _logger.LogError(ex, "Error while scanning container {ContainerName}", _scanSizeOf.Name);
SizeSizeScanContainer.Exceptions.Add(ex);
} }
IsRunning = false;
await SizeSizeScanContainer.StopLoadingAsync();
} }
//TODO: make static //TODO: make static
private async Task TraverseTree( private async Task TraverseTree(
IContainer realContainer, IContainer realContainer,
IContainerSizeScanContainer container) ISizeScanContainer sizeScanContainer)
{ {
if(_cancelled) return;
await realContainer.WaitForLoaded();
var resolvedItems = new List<IItem>(realContainer.Items.Count); var resolvedItems = new List<IItem>(realContainer.Items.Count);
foreach (var item in realContainer.Items) foreach (var item in realContainer.Items)
{ {
@@ -78,34 +99,59 @@ public class SizeScanTask : ISizeScanTask
foreach (var element in resolvedItems.OfType<IElement>()) foreach (var element in resolvedItems.OfType<IElement>())
{ {
if(_cancelled) return;
var fileExtension = element.GetExtension<FileExtension>(); var fileExtension = element.GetExtension<FileExtension>();
if (fileExtension?.Size is not { } size) continue; if (fileExtension?.Size is not { } size) continue;
var childName = container.FullName!.GetChild(element.Name).Path; var sizeProperty = new DeclarativeProperty<long>(size);
await container.AddSizeSourceAsync(new DeclarativeProperty<long>(size));
container.Items.Add(new AbsolutePath( var childName = sizeScanContainer.FullName!.GetChild(element.Name).Path;
await sizeScanContainer.AddSizeSourceAsync(sizeProperty);
sizeScanContainer.Items.Add(new AbsolutePath(
_timelessContentProvider, _timelessContentProvider,
PointInTime.Present, PointInTime.Present,
new FullName(childName), new FullName(childName),
AbsolutePathType.Element)); 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<IContainer>()) foreach (var childContainer in resolvedItems.OfType<IContainer>())
{ {
var childName = container.FullName!.GetChild(childContainer.Name).Path; if(_cancelled) return;
var childSearchContainer = new ContainerSizeScanContainer(_containerScanSnapshotProvider)
var childName = sizeScanContainer.FullName!.GetChild(childContainer.Name).Path;
var childSearchContainer = new SizeScanContainer
{ {
Name = childContainer.Name, Name = childContainer.Name,
DisplayName = childContainer.DisplayName, DisplayName = childContainer.DisplayName,
FullName = new FullName(childName), FullName = new FullName(childName),
NativePath = new NativePath(childName), NativePath = new NativePath(childName),
Parent = new AbsolutePath(_timelessContentProvider, container), Parent = new AbsolutePath(_timelessContentProvider, sizeScanContainer),
RealContainer = childContainer RealContainer = childContainer,
Provider = _containerScanSnapshotProvider,
Status = _containerStatusDebounced,
SizeScanTask = this
}; };
container.ChildContainers.Add(childSearchContainer); sizeScanContainer.ChildContainers.Add(childSearchContainer);
await container.AddSizeSourceAsync(childSearchContainer.Size); sizeScanContainer.SizeItems.Add(childSearchContainer);
container.Items.Add(new AbsolutePath( await sizeScanContainer.AddSizeSourceAsync(childSearchContainer.Size);
sizeScanContainer.Items.Add(new AbsolutePath(
_timelessContentProvider, _timelessContentProvider,
PointInTime.Present, PointInTime.Present,
new FullName(childName), new FullName(childName),
@@ -113,5 +159,16 @@ public class SizeScanTask : ISizeScanTask
await TraverseTree(childContainer, childSearchContainer); 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);
}
} }
} }

View File

@@ -9,10 +9,10 @@ public static class Startup
{ {
public static IServiceCollection AddContainerSizeScanner(this IServiceCollection services) public static IServiceCollection AddContainerSizeScanner(this IServiceCollection services)
{ {
services.TryAddSingleton<IContainerScanSnapshotProvider, ContainerScanSnapshotProvider>(); services.TryAddSingleton<IContainerScanSnapshotProvider, ContainerScanProvider>();
services.AddSingleton<IContentProvider>(sp => sp.GetRequiredService<IContainerScanSnapshotProvider>()); services.AddSingleton<IContentProvider>(sp => sp.GetRequiredService<IContainerScanSnapshotProvider>());
services.AddTransient<ISizeScanTask, SizeScanTask>(); services.AddTransient<ISizeScanTask, SizeScanTask>();
services.AddTransient<IItemPreviewProvider, ContainerSizePreviewProvider>(); services.AddTransient<IItemPreviewProvider, PreviewProvider>();
return services; return services;
} }
} }

View File

@@ -10,6 +10,6 @@ public class DoubleTextPreview : IPreviewElement
public IObservable<List<ItemNamePart>> Text1 { get; init; } = new BehaviorSubject<List<ItemNamePart>>(new()); public IObservable<List<ItemNamePart>> Text1 { get; init; } = new BehaviorSubject<List<ItemNamePart>>(new());
public IObservable<List<ItemNamePart>> Text2 { get; init; } = new BehaviorSubject<List<ItemNamePart>>(new()); public IObservable<List<ItemNamePart>> Text2 { get; init; } = new BehaviorSubject<List<ItemNamePart>>(new());
public PreviewType PreviewType { get; } = PreviewType.DoubleTextList; public PreviewType PreviewType => PreviewType.DoubleTextList;
object IPreviewElement.PreviewType => PreviewType; object IPreviewElement.PreviewType => PreviewType;
} }

View File

@@ -1,9 +0,0 @@
namespace FileTime.App.Core.Models.Enums;
public enum ItemAttributeType
{
File,
Element,
Container,
SizeContainer
}

View File

@@ -0,0 +1,8 @@
using DeclarativeProperty;
namespace FileTime.App.Core.Models.Traits;
public interface ISizeProvider
{
IDeclarativeProperty<long> Size { get; }
}

View File

@@ -1,8 +1,9 @@
using DeclarativeProperty;
using FileTime.App.Core.ViewModels.ItemPreview; using FileTime.App.Core.ViewModels.ItemPreview;
namespace FileTime.App.Core.Services; namespace FileTime.App.Core.Services;
public interface IItemPreviewService public interface IItemPreviewService
{ {
IObservable<IItemPreviewViewModel?> ItemPreview { get; } IDeclarativeProperty<IItemPreviewViewModel?> ItemPreview { get; }
} }

View File

@@ -13,6 +13,7 @@ public interface IAppState
IDeclarativeProperty<ViewMode> ViewMode { get; } IDeclarativeProperty<ViewMode> ViewMode { get; }
DeclarativeProperty<string?> RapidTravelText { get; } DeclarativeProperty<string?> RapidTravelText { get; }
ITimelineViewModel TimelineViewModel { get; } ITimelineViewModel TimelineViewModel { get; }
IDeclarativeProperty<string?> ContainerStatus { get; }
void AddTab(ITabViewModel tabViewModel); void AddTab(ITabViewModel tabViewModel);
void RemoveTab(ITabViewModel tabViewModel); void RemoveTab(ITabViewModel tabViewModel);

View File

@@ -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<IContainer, ITabViewModel, ItemViewModelType>
{
long Size { get; set; }
}

View File

@@ -1,11 +1,14 @@
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.Models.Traits;
using FileTime.Core.Models; using FileTime.Core.Models;
using InitableService; using InitableService;
namespace FileTime.App.Core.ViewModels; namespace FileTime.App.Core.ViewModels;
public interface IElementViewModel : IItemViewModel, IInitable<IElement, ITabViewModel, ItemViewModelType> public interface IElementViewModel :
IItemViewModel,
ISizeProvider,
IInitable<IElement, ITabViewModel, ItemViewModelType>
{ {
IElement? Element { get; } IElement? Element { get; }
long? Size { get; set; }
} }

View File

@@ -13,7 +13,7 @@ public interface IItemViewModel : IInitable<IItem, ITabViewModel, ItemViewModelT
IDeclarativeProperty<bool> IsSelected { get; set; } IDeclarativeProperty<bool> IsSelected { get; set; }
IDeclarativeProperty<bool>? IsMarked { get; set; } IDeclarativeProperty<bool>? IsMarked { get; set; }
IDeclarativeProperty<bool> IsAlternative { get; } IDeclarativeProperty<bool> IsAlternative { get; }
IObservable<ItemViewMode> ViewMode { get; set; } IDeclarativeProperty<ItemViewMode> ViewMode { get; set; }
DateTime? CreatedAt { get; set; } DateTime? CreatedAt { get; set; }
string? Attributes { get; set; } string? Attributes { get; set; }
bool EqualsTo(IItemViewModel? itemViewModel); bool EqualsTo(IItemViewModel? itemViewModel);

View File

@@ -1,4 +1,5 @@
using System.Reactive.Linq; using System.Reactive.Linq;
using DeclarativeProperty;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.App.Core.ViewModels.ItemPreview; using FileTime.App.Core.ViewModels.ItemPreview;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -10,7 +11,7 @@ public class ItemPreviewService : IItemPreviewService
{ {
private readonly IServiceProvider _serviceProvider; private readonly IServiceProvider _serviceProvider;
private readonly IEnumerable<IItemPreviewProvider> _itemPreviewProviders; private readonly IEnumerable<IItemPreviewProvider> _itemPreviewProviders;
public IObservable<IItemPreviewViewModel?> ItemPreview { get; } public IDeclarativeProperty<IItemPreviewViewModel?> ItemPreview { get; }
public ItemPreviewService( public ItemPreviewService(
IAppState appState, IAppState appState,
@@ -21,18 +22,15 @@ public class ItemPreviewService : IItemPreviewService
_itemPreviewProviders = itemPreviewProviders; _itemPreviewProviders = itemPreviewProviders;
ItemPreview = appState ItemPreview = appState
.SelectedTab .SelectedTab
.Select(t => .Map(t => t.CurrentSelectedItem)
t?.CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250))
?? Observable.Return<IItemViewModel?>(null))
.Switch() .Switch()
.Select(item => .Debounce(TimeSpan.FromMilliseconds(250))
.Map(async (item, _) =>
item == null item == null
? Observable.Return<IItemPreviewViewModel?>(null) ? null
: Observable.FromAsync(async () => await Map(item)) : await Map(item)
) )
.Switch() .DistinctUntilChanged();
.Publish(null)
.RefCount();
} }
private async Task<IItemPreviewViewModel?> Map(IItemViewModel itemViewModel) private async Task<IItemPreviewViewModel?> Map(IItemViewModel itemViewModel)

View File

@@ -112,6 +112,7 @@ public class TabPersistenceService : ITabPersistenceService
try try
{ {
if (tab.Path == null) continue; if (tab.Path == null) continue;
if (_contentProvidersNotToRestore.Any(p => tab.Path.StartsWith(p))) continue;
IContainer? container = null; IContainer? container = null;
var path = FullName.CreateSafe(tab.Path); var path = FullName.CreateSafe(tab.Path);

View File

@@ -332,7 +332,11 @@ public class NavigationUserCommandHandlerService : UserCommandHandlerServiceBase
} }
else if (tabViewModel == null) else if (tabViewModel == null)
{ {
var tab = await _serviceProvider.GetAsyncInitableResolver<IContainer>(_currentLocation?.Value ?? _localContentProvider) var newLocation = _currentLocation?.Value?.FullName is { } fullName
? (IContainer) await _timelessContentProvider.GetItemByFullNameAsync(fullName, PointInTime.Present)
: _localContentProvider;
var tab = await _serviceProvider.GetAsyncInitableResolver<IContainer>(newLocation)
.GetRequiredServiceAsync<ITab>(); .GetRequiredServiceAsync<ITab>();
var newTabViewModel = _serviceProvider.GetInitableResolver(tab, number).GetRequiredService<ITabViewModel>(); var newTabViewModel = _serviceProvider.GetInitableResolver(tab, number).GetRequiredService<ITabViewModel>();

View File

@@ -66,7 +66,7 @@ public class ToolUserCommandHandlerService : UserCommandHandlerServiceBase
if (_currentLocation?.Value is null) return; if (_currentLocation?.Value is null) return;
var searchTask = _containerScanSnapshotProvider.StartSizeScan(_currentLocation.Value); 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); await _userCommandHandlerService.HandleCommandAsync(openContainerCommand);
} }

View File

@@ -18,7 +18,6 @@ public static class Startup
serviceCollection.TryAddTransient<IContainerViewModel, ContainerViewModel>(); serviceCollection.TryAddTransient<IContainerViewModel, ContainerViewModel>();
serviceCollection.TryAddTransient<IElementViewModel, ElementViewModel>(); serviceCollection.TryAddTransient<IElementViewModel, ElementViewModel>();
serviceCollection.TryAddTransient<IFileViewModel, FileViewModel>(); serviceCollection.TryAddTransient<IFileViewModel, FileViewModel>();
serviceCollection.TryAddTransient<IContainerSizeContainerViewModel, ContainerSizeContainerViewModel>();
serviceCollection.TryAddTransient<IItemNameConverterService, ItemNameConverterService>(); serviceCollection.TryAddTransient<IItemNameConverterService, ItemNameConverterService>();
serviceCollection.TryAddTransient<ElementPreviewViewModel>(); serviceCollection.TryAddTransient<ElementPreviewViewModel>();
serviceCollection.TryAddSingleton<IUserCommandHandlerService, UserCommandHandlerService>(); serviceCollection.TryAddSingleton<IUserCommandHandlerService, UserCommandHandlerService>();

View File

@@ -5,6 +5,8 @@ using DeclarativeProperty;
using DynamicData; using DynamicData;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.ViewModels.Timeline; using FileTime.App.Core.ViewModels.Timeline;
using FileTime.Core.Models;
using FileTime.Core.Models.ContainerTraits;
using MvvmGen; using MvvmGen;
using MoreLinq; using MoreLinq;
@@ -27,6 +29,8 @@ public abstract partial class AppStateBase : IAppState
public IDeclarativeProperty<ITabViewModel?> SelectedTab { get; private set; } public IDeclarativeProperty<ITabViewModel?> SelectedTab { get; private set; }
public DeclarativeProperty<string?> RapidTravelText { get; private set; } public DeclarativeProperty<string?> RapidTravelText { get; private set; }
public IDeclarativeProperty<string?> ContainerStatus { get; private set; }
partial void OnInitialize() partial void OnInitialize()
{ {
RapidTravelText = new(""); RapidTravelText = new("");
@@ -40,6 +44,12 @@ public abstract partial class AppStateBase : IAppState
); );
Tabs = new ReadOnlyObservableCollection<ITabViewModel>(_tabs); Tabs = new ReadOnlyObservableCollection<ITabViewModel>(_tabs);
ContainerStatus = SelectedTab
.Map(t => t?.CurrentLocation)
.Switch()
.Map(c => c is IStatusProviderContainer statusProvider ? statusProvider.Status : null)
.Switch();
} }
public void AddTab(ITabViewModel tabViewModel) public void AddTab(ITabViewModel tabViewModel)

View File

@@ -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);
}

View File

@@ -1,3 +1,4 @@
using DeclarativeProperty;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.Services; using FileTime.App.Core.Services;
using FileTime.Core.Models; using FileTime.Core.Models;
@@ -10,13 +11,12 @@ public partial class ElementViewModel : ItemViewModel, IElementViewModel
{ {
public IElement? Element => BaseItem as Element; public IElement? Element => BaseItem as Element;
[Property]
private long? _size;
public ElementViewModel(IItemNameConverterService itemNameConverterService, IAppState appState) : base(itemNameConverterService, appState) public ElementViewModel(IItemNameConverterService itemNameConverterService, IAppState appState) : base(itemNameConverterService, appState)
{ {
} }
public void Init(IElement item, ITabViewModel parentTab, ItemViewModelType itemViewModelType) public void Init(IElement item, ITabViewModel parentTab, ItemViewModelType itemViewModelType)
=> Init((IItem)item, parentTab, itemViewModelType); => Init((IItem)item, parentTab, itemViewModelType);
public IDeclarativeProperty<long> Size { get; protected set; } = new DeclarativeProperty<long>(0);
} }

View File

@@ -1,3 +1,4 @@
using DeclarativeProperty;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.Services; using FileTime.App.Core.Services;
using FileTime.Core.Models; 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) public void Init(IElement item, FileExtension fileExtension, ITabViewModel parentTab, ItemViewModelType itemViewModelType)
{ {
Init((IElement)item, parentTab, itemViewModelType); Init(item, parentTab, itemViewModelType);
Size = fileExtension.Size; Size = new DeclarativeProperty<long>(fileExtension.Size ?? 0);
} }
} }

View File

@@ -1,3 +1,4 @@
using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using DeclarativeProperty; using DeclarativeProperty;
@@ -27,7 +28,7 @@ public abstract partial class ItemViewModel : IItemViewModel
[Property] private IDeclarativeProperty<bool>? _isMarked; [Property] private IDeclarativeProperty<bool>? _isMarked;
[Property] private IObservable<ItemViewMode> _viewMode; [Property] private IDeclarativeProperty<ItemViewMode> _viewMode;
[Property] private DateTime? _createdAt; [Property] private DateTime? _createdAt;
@@ -75,15 +76,23 @@ public abstract partial class ItemViewModel : IItemViewModel
? parentTab.CurrentSelectedItem.Map(EqualsTo) ? parentTab.CurrentSelectedItem.Map(EqualsTo)
: new DeclarativeProperty<bool>(IsInDeepestPath()); : new DeclarativeProperty<bool>(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; Attributes = item.Attributes;
CreatedAt = item.CreatedAt; CreatedAt = item.CreatedAt;
} }
private ItemViewMode GenerateViewMode(bool isMarked, bool isSelected, bool isAlternative) private Task<ItemViewMode> GenerateViewMode(bool isMarked, bool isSelected, bool isAlternative)
=> (isMarked, isSelected, isAlternative) switch {
var result = (isMarked, isSelected, isAlternative) switch
{ {
(true, true, _) => ItemViewMode.MarkedSelected, (true, true, _) => ItemViewMode.MarkedSelected,
(true, false, true) => ItemViewMode.MarkedAlternative, (true, false, true) => ItemViewMode.MarkedAlternative,
@@ -93,11 +102,12 @@ public abstract partial class ItemViewModel : IItemViewModel
_ => ItemViewMode.Default _ => ItemViewMode.Default
}; };
return Task.FromResult(result);
}
public bool EqualsTo(IItemViewModel? itemViewModel) public bool EqualsTo(IItemViewModel? itemViewModel)
{ => BaseItem?.FullName?.Path is { } path && path == itemViewModel?.BaseItem?.FullName?.Path;
return BaseItem?.FullName?.Path is string path && path == itemViewModel?.BaseItem?.FullName?.Path;
}
private bool IsInDeepestPath() private bool IsInDeepestPath()
{ {

View File

@@ -162,7 +162,7 @@ public partial class TabViewModel : ITabViewModel
.Selecting(i => MapItem(i)) .Selecting(i => MapItem(i))
.Ordering(i => i.Type) .Ordering(i => i.Type)
.ThenOrdering(i => i.Name) .ThenOrdering(i => i.Name)
.Selecting(i => MapItemToViewModel(i, ItemViewModelType.SelectedChild)); .Selecting(i => MapItemToViewModel(i, ItemViewModelType.Parent));
return items; return items;
}); });

View File

@@ -140,7 +140,7 @@ public partial class FrequencyNavigationService : IFrequencyNavigationService, I
{ {
return _containerScores return _containerScores
.Where(c => c.Key.Contains(searchText, StringComparison.OrdinalIgnoreCase)) .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) .Select(c => c.Key)
.ToList(); .ToList();
} }

View File

@@ -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;
}
}

View File

@@ -0,0 +1,6 @@
namespace FileTime.Core.Models.ContainerTraits;
public interface IEscHandlerContainer
{
Task<ContainerEscapeResult> HandleEsc();
}

View File

@@ -0,0 +1,8 @@
using DeclarativeProperty;
namespace FileTime.Core.Models.ContainerTraits;
public interface IStatusProviderContainer
{
IDeclarativeProperty<string> Status { get; }
}

View File

@@ -1,5 +1,4 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using DynamicData;
using FileTime.Core.ContentAccess; using FileTime.Core.ContentAccess;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;

View File

@@ -6,6 +6,11 @@ public class ReadOnlyExtensionCollection : IEnumerable<object>
{ {
private readonly ExtensionCollection _collection; private readonly ExtensionCollection _collection;
public ReadOnlyExtensionCollection()
{
_collection = new ExtensionCollection();
}
public ReadOnlyExtensionCollection(ExtensionCollection collection) public ReadOnlyExtensionCollection(ExtensionCollection collection)
{ {
_collection = collection; _collection = collection;

View File

@@ -1,7 +1,6 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using DynamicData;
using FileTime.Core.ContentAccess; using FileTime.Core.ContentAccess;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Timeline; using FileTime.Core.Timeline;

View File

@@ -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;
}

View File

@@ -1,46 +1,29 @@
using System.Globalization; using System.Globalization;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
using ByteSizeLib;
namespace FileTime.GuiApp.Converters; namespace FileTime.GuiApp.Converters;
public class FormatSizeConverter : IValueConverter public class FormatSizeConverter : IValueConverter
{ {
private const long OneKiloByte = 1024; public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
private const long OneMegaByte = OneKiloByte * 1024; (value, int.TryParse(parameter?.ToString(), out var prec)) switch
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
{ {
(long size, true) => ToSizeString(size, prec), (long size, true) => ToSizeString(size, prec),
(long size, false) => ToSizeString(size), (long size, false) => ToSizeString(size),
(null, _) => "...", (null, _) => "...",
_ => value _ => value
}; };
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
throw new NotImplementedException(); 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 = new ByteSize(fileSize);
var (size, suffix) = fileSize switch return precision == null? size.ToString()
{ : size.ToString("0." + new string('#', precision.Value));
> 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;
} }
} }

View File

@@ -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();
}

View File

@@ -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<object?> 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);
}

View File

@@ -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<object?> 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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -26,18 +26,13 @@
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}" SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"
x:Key="ItemViewModeToBackgroundConverter" /> x:Key="ItemViewModeToBackgroundConverter" />
<converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter" /> <converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter" />
<converters:ItemViewModelIsAttributeTypeConverter x:Key="ItemViewModelIsAttributeTypeConverter" />
<converters:ItemViewModelIsAttributeTypeConverter Invert="true"
x:Key="ItemViewModelIsNotAttributeTypeConverter" />
<converters:GetFileExtensionConverter x:Key="GetFileExtensionConverter" /> <converters:GetFileExtensionConverter x:Key="GetFileExtensionConverter" />
<converters:FormatSizeConverter x:Key="FormatSizeConverter" /> <converters:FormatSizeConverter x:Key="FormatSizeConverter" />
<converters:DateTimeConverter x:Key="DateTimeConverter" /> <converters:DateTimeConverter x:Key="DateTimeConverter" />
<converters:SplitStringConverter x:Key="SplitStringConverter" /> <converters:SplitStringConverter x:Key="SplitStringConverter" />
<converters:CompareConverter x:Key="EqualsConverter" /> <converters:CompareConverter x:Key="EqualsConverter" />
<converters:CompareConverter ComparisonCondition="{x:Static converters:ComparisonCondition.NotEqual}" <converters:CompareConverter ComparisonCondition="{x:Static converters:ComparisonCondition.NotEqual}" x:Key="NotEqualsConverter" />
x:Key="NotEqualsConverter" /> <converters:CompareConverter ComparisonCondition="{x:Static converters:ComparisonCondition.GreaterThan}" x:Key="GreaterThanConverter" />
<converters:CompareConverter ComparisonCondition="{x:Static converters:ComparisonCondition.GreaterThan}"
x:Key="GreaterThanConverter" />
<converters:ExceptionToStringConverter x:Key="ExceptionToStringConverter" /> <converters:ExceptionToStringConverter x:Key="ExceptionToStringConverter" />
<converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter" /> <converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter" />
<converters:ItemToImageConverter x:Key="ItemToImageConverter" /> <converters:ItemToImageConverter x:Key="ItemToImageConverter" />
@@ -47,6 +42,16 @@
x:Key="PathPreformatter" /> x:Key="PathPreformatter" />
<converters:ContextMenuGenerator x:Key="ContextMenuGenerator" /> <converters:ContextMenuGenerator x:Key="ContextMenuGenerator" />
<converters:TextDecorationConverter x:Key="TextDecorationConverter" /> <converters:TextDecorationConverter x:Key="TextDecorationConverter" />
<converters:ItemSizeToSizeConverter x:Key="ItemSizeToSizeConverter" />
<converters:ItemSizeToBrushConverter x:Key="ItemSizeToBrushConverter" />
<converters:ItemSizeToBrushConverter
HueDiff="90"
Lightness="0.25"
x:Key="ItemSizeToForegroundBrushConverter" />
<converters:IsTypeConverter x:Key="IsTypeConverter" />
<converters:IsTypeConverter Invert="True" x:Key="IsNotTypeConverter" />
<converters:DebugConverter x:Key="DebugConverter" />
</ResourceDictionary> </ResourceDictionary>
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -5,6 +5,7 @@ using FileTime.App.Core.UserCommand;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Extensions; using FileTime.Core.Extensions;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Models.ContainerTraits;
using FileTime.GuiApp.Configuration; using FileTime.GuiApp.Configuration;
using FileTime.GuiApp.Extensions; using FileTime.GuiApp.Extensions;
using FileTime.GuiApp.Models; using FileTime.GuiApp.Models;
@@ -66,20 +67,23 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
if (key == Key.Escape) 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) if ((_openModals.Collection?.Count ?? 0) > 0)
{ {
_modalService.CloseModal(_openModals.Collection!.Last()); _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) if (escapeResult.NavigateTo != null)
{ {
setHandled(true); setHandled(true);
_appState.PreviousKeys.Clear(); _appState.PreviousKeys.Clear();
await _appState.SelectedTab.OpenContainer(escapeResult.NavigateTo); if (_appState.SelectedTab.Value?.Tab is { } selectedTab)
{
await selectedTab.SetCurrentLocation(escapeResult.NavigateTo);
}
} }
else else
{ {
@@ -92,7 +96,7 @@ public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
doGeneralReset = true; doGeneralReset = true;
} }
} }
}*/ }
if (doGeneralReset) if (doGeneralReset)
{ {

View File

@@ -1,52 +1,49 @@
<UserControl <UserControl
Background="{Binding ViewMode.Value, Converter={StaticResource ItemViewModeToBackgroundConverter}}"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d"
x:Class="FileTime.GuiApp.Views.ItemView" x:Class="FileTime.GuiApp.Views.ItemView"
x:CompileBindings="True"
x:DataType="appcore:IItemViewModel"
x:Name="ItemRoot"
xmlns="https://github.com/avaloniaui" xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:appcore="using:FileTime.App.Core.ViewModels" xmlns:appcore="using:FileTime.App.Core.ViewModels"
xmlns:appcoreenums="using:FileTime.App.Core.Models.Enums" xmlns:appcoreenums="using:FileTime.App.Core.Models.Enums"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:guiappvm="using:FileTime.GuiApp.ViewModels" xmlns:guiappvm="using:FileTime.GuiApp.ViewModels"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="ItemRoot" xmlns:traits="clr-namespace:FileTime.App.Core.Models.Traits;assembly=FileTime.App.Core.Abstraction"
HorizontalAlignment="Stretch" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
HorizontalContentAlignment="Stretch"
d:DesignHeight="450"
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="appcore:IItemViewModel"
Background="{Binding ViewMode^, Converter={StaticResource ItemViewModeToBackgroundConverter}}"
mc:Ignorable="d">
<Grid <Grid
x:Name="RootGrid" ColumnDefinitions="20,*,Auto"
Margin="3" Margin="3"
ColumnDefinitions="20,*,Auto"> x:Name="RootGrid">
<Grid.Styles> <Grid.Styles>
<Style Selector="TextBlock"> <Style Selector="TextBlock">
<Setter x:CompileBindings="False" Property="Foreground" Value="{Binding DataContext.ViewMode^, Converter={StaticResource ItemViewModeToForegroundConverter}, ElementName=ItemRoot}" /> <Setter Property="Foreground" Value="{Binding DataContext.ViewMode.Value, Converter={StaticResource ItemViewModeToForegroundConverter}, ElementName=ItemRoot}" x:CompileBindings="False" />
</Style> </Style>
</Grid.Styles> </Grid.Styles>
<Image <Image
Width="18"
Height="18" Height="18"
HorizontalAlignment="Left" HorizontalAlignment="Left"
Source="{Binding Converter={StaticResource ItemToImageConverter}}"
VerticalAlignment="Center" VerticalAlignment="Center"
Source="{Binding Converter={StaticResource ItemToImageConverter}}" /> Width="18" />
<ItemsControl <ItemsControl
Grid.Column="1" Grid.Column="1"
Margin="5,0,0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
Margin="5,0,0,0"
VerticalAlignment="Center"> VerticalAlignment="Center">
<ItemsControl.ItemsSource> <ItemsControl.ItemsSource>
<MultiBinding Converter="{StaticResource NamePartShrinkerConverter}"> <MultiBinding Converter="{StaticResource NamePartShrinkerConverter}">
<MultiBinding.Bindings> <MultiBinding.Bindings>
<Binding Path="DisplayName^" /> <Binding Path="DisplayName^" />
<Binding <Binding ElementName="RootGrid" Path="Bounds.Width" />
ElementName="RootGrid" <Binding ElementName="ItemRoot" Path="ShowAttributes" />
Path="Bounds.Width" />
<Binding
ElementName="ItemRoot"
Path="ShowAttributes" />
</MultiBinding.Bindings> </MultiBinding.Bindings>
</MultiBinding> </MultiBinding>
</ItemsControl.ItemsSource> </ItemsControl.ItemsSource>
@@ -58,96 +55,55 @@
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="guiappvm:ItemNamePartViewModel"> <DataTemplate x:DataType="guiappvm:ItemNamePartViewModel">
<Grid> <Grid>
<TextBlock <TextBlock Text="{Binding Text}" TextDecorations="{Binding TextDecorations}" />
Text="{Binding Text}"
TextDecorations="{Binding TextDecorations}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<Grid <Grid Grid.Column="2" IsVisible="{Binding ShowAttributes, ElementName=ItemRoot}">
Grid.Column="2" <StackPanel HorizontalAlignment="Right" Orientation="Horizontal">
IsVisible="{Binding ShowAttributes, ElementName=ItemRoot}"> <TextBlock
<Grid Classes="SmallText"
HorizontalAlignment="Right" IsVisible="{Binding Converter={StaticResource IsTypeConverter}, ConverterParameter={x:Type appcore:IElementViewModel}}"
x:DataType="appcore:IFileViewModel" Text="{Binding BaseItem.DisplayName, Converter={StaticResource GetFileExtensionConverter}}"
ColumnDefinitions="50,50,90,40,45" Width="50" />
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter}, ConverterParameter={x:Static appcoreenums:ItemAttributeType.File}}"> <Grid DataContext="{Binding BaseItem}" IsVisible="{Binding Converter={StaticResource IsTypeConverter}, ConverterParameter={x:Type traits:ISizeProvider}}">
<TextBlock
Classes="SmallText"
Text="{Binding Size.Value, Converter={StaticResource FormatSizeConverter}}"
TextAlignment="Right"
Width="60"
x:DataType="traits:ISizeProvider" />
</Grid>
<Grid IsVisible="{Binding BaseItem, Converter={StaticResource IsNotTypeConverter}, ConverterParameter={x:Type traits:ISizeProvider}}">
<TextBlock <TextBlock
HorizontalAlignment="Right"
Classes="SmallText" Classes="SmallText"
Text="{Binding BaseItem.DisplayName, Converter={StaticResource GetFileExtensionConverter}}" /> IsVisible="{Binding Converter={StaticResource IsTypeConverter}, ConverterParameter={x:Type traits:ISizeProvider}}"
<TextBlock Text="{Binding Size.Value, Converter={StaticResource FormatSizeConverter}}"
Grid.Column="1" TextAlignment="Right"
HorizontalAlignment="Right" Width="60"
Classes="SmallText" x:DataType="traits:ISizeProvider" />
Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}" />
<TextBlock
Grid.Column="2"
HorizontalAlignment="Right"
Classes="SmallText"
Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}" />
<TextBlock
Grid.Column="3"
HorizontalAlignment="Right"
Classes="SmallText"
Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}" />
<TextBlock
Grid.Column="4"
HorizontalAlignment="Right"
Classes="SmallText"
Text="{Binding BaseItem.Attributes}" />
</Grid> </Grid>
<Grid <TextBlock
HorizontalAlignment="Right" Classes="SmallText"
x:DataType="appcore:IContainerViewModel" Text="{Binding CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}"
ColumnDefinitions="90,40,45" TextAlignment="Right"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter}, ConverterParameter={x:Static appcoreenums:ItemAttributeType.Container}}"> Width="95" />
<TextBlock
Classes="SmallText"
Text="{Binding CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}"
TextAlignment="Right"
Width="35" />
<TextBlock
Classes="SmallText"
Text="{Binding BaseItem.Attributes}"
TextAlignment="Right"
Width="45" />
<TextBlock </StackPanel>
HorizontalAlignment="Right"
Classes="SmallText"
Text="{Binding CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=yyyy-MM-dd}" />
<TextBlock
Grid.Column="1"
HorizontalAlignment="Right"
Classes="SmallText"
Text="{Binding BaseItem.CreatedAt, Converter={StaticResource DateTimeConverter}, ConverterParameter=hh:mm}" />
<TextBlock
Grid.Column="2"
HorizontalAlignment="Right"
Classes="SmallText"
Text="{Binding BaseItem.Attributes}" />
</Grid>
<Grid
HorizontalAlignment="Right"
x:DataType="appcore:IContainerSizeContainerViewModel"
ColumnDefinitions="50,90,40,45"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter}, ConverterParameter={x:Static appcoreenums:ItemAttributeType.SizeContainer}}">
<TextBlock
HorizontalAlignment="Right"
Classes="SmallText"
Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}" />
</Grid>
<Grid
HorizontalAlignment="Right"
x:DataType="appcore:IElementViewModel"
ColumnDefinitions="50,50"
IsVisible="{Binding Converter={StaticResource ItemViewModelIsAttributeTypeConverter}, ConverterParameter={x:Static appcoreenums:ItemAttributeType.Element}}">
<TextBlock
HorizontalAlignment="Right"
Classes="SmallText"
Text="{Binding BaseItem.DisplayName, Converter={StaticResource GetFileExtensionConverter}}" />
<TextBlock
Grid.Column="1"
HorizontalAlignment="Right"
Classes="SmallText"
Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}" />
</Grid>
</Grid> </Grid>
</Grid> </Grid>
</UserControl> </UserControl>

View File

@@ -61,8 +61,8 @@
<Grid ColumnDefinitions="*, Auto"> <Grid ColumnDefinitions="*, Auto">
<StackPanel Margin="20,10" Orientation="Horizontal"> <StackPanel Margin="20,10" Orientation="Horizontal">
<local:PathPresenter DataContext="{Binding AppState.SelectedTab^.CurrentLocation^.FullName.Path, Converter={StaticResource PathPreformatter}}" /> <local:PathPresenter DataContext="{Binding AppState.SelectedTab.Value.CurrentLocation^.FullName.Path, Converter={StaticResource PathPreformatter}}" />
<TextBlock Foreground="{StaticResource AccentBrush}" Text="{Binding AppState.SelectedTab^.CurrentSelectedItem.Value.DisplayNameText}" /> <TextBlock Foreground="{StaticResource AccentBrush}" Text="{Binding AppState.SelectedTab.Value.CurrentSelectedItem.Value.DisplayNameText}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel
Grid.Column="1" Grid.Column="1"
@@ -153,7 +153,7 @@
<TextBlock <TextBlock
Classes="SmallText" Classes="SmallText"
Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}" Text="{Binding Free, Converter={StaticResource FormatSizeConverter}}"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
<TextBlock <TextBlock
@@ -163,7 +163,7 @@
<TextBlock <TextBlock
Classes="SmallText" Classes="SmallText"
Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}}"
VerticalAlignment="Center" /> VerticalAlignment="Center" />
</StackPanel> </StackPanel>
@@ -437,7 +437,7 @@
AutoScrollToSelectedItem="True" AutoScrollToSelectedItem="True"
Classes="ContentListView" Classes="ContentListView"
IsTabStop="True" IsTabStop="True"
ItemsSource="{Binding AppState.SelectedTab^.ParentsChildren.Value}" ItemsSource="{Binding AppState.SelectedTab.Value.ParentsChildren.Value}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Visible"> ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
@@ -459,7 +459,7 @@
Width="1" /> Width="1" />
<Grid Grid.Column="2" RowDefinitions="Auto,*"> <Grid Grid.Column="2" RowDefinitions="Auto,*">
<Grid IsVisible="{Binding AppState.SelectedTab^.CurrentLocation.Value.IsLoading^, FallbackValue=False}"> <Grid IsVisible="{Binding AppState.SelectedTab.Value.CurrentLocation.Value.IsLoading^, FallbackValue=False}">
<Image <Image
Classes="LoadingAnimation" Classes="LoadingAnimation"
Height="40" Height="40"
@@ -471,10 +471,10 @@
Classes="ContentListView" Classes="ContentListView"
Grid.Row="1" Grid.Row="1"
IsTabStop="True" IsTabStop="True"
ItemsSource="{Binding AppState.SelectedTab^.CurrentItems.Value}" ItemsSource="{Binding AppState.SelectedTab.Value.CurrentItems.Value}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectedItem="{Binding AppState.SelectedTab^.CurrentSelectedItem.Value}" SelectedItem="{Binding AppState.SelectedTab.Value.CurrentSelectedItem.Value}"
x:Name="CurrentItems"> x:Name="CurrentItems">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel"> <DataTemplate x:DataType="corevm:IItemViewModel">
@@ -488,7 +488,7 @@
Foreground="{DynamicResource ErrorBrush}" Foreground="{DynamicResource ErrorBrush}"
Grid.Row="1" Grid.Row="1"
HorizontalAlignment="Center" 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" Margin="10"
x:Name="CurrentEmpty"> x:Name="CurrentEmpty">
Empty Empty
@@ -504,11 +504,11 @@
Width="1" /> Width="1" />
<Grid Grid.Column="4"> <Grid Grid.Column="4">
<Grid IsVisible="{Binding ItemPreviewService.ItemPreview^, Converter={x:Static ObjectConverters.IsNull}}"> <Grid IsVisible="{Binding ItemPreviewService.ItemPreview.Value, Converter={x:Static ObjectConverters.IsNull}}">
<Grid IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren.Value, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}"> <Grid IsVisible="{Binding AppState.SelectedTab.Value.SelectedsChildren.Value, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}">
<Grid RowDefinitions="Auto, Auto, *"> <Grid RowDefinitions="Auto, Auto, *">
<Grid IsVisible="{Binding AppState.SelectedTab^.CurrentSelectedItemAsContainer.Value.Container.IsLoading^, FallbackValue=False}"> <Grid IsVisible="{Binding AppState.SelectedTab.Value.CurrentSelectedItemAsContainer.Value.Container.IsLoading^, FallbackValue=False}">
<Image <Image
Classes="LoadingAnimation" Classes="LoadingAnimation"
Height="40" Height="40"
@@ -517,7 +517,7 @@
</Grid> </Grid>
<ItemsRepeater Grid.Row="1" ItemsSource="{Binding AppState.SelectedTab^.CurrentLocation.Value.Exceptions}"> <ItemsRepeater Grid.Row="1" ItemsSource="{Binding AppState.SelectedTab.Value.CurrentLocation.Value.Exceptions}">
<ItemsRepeater.ItemTemplate> <ItemsRepeater.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock <TextBlock
@@ -532,8 +532,8 @@
AutoScrollToSelectedItem="True" AutoScrollToSelectedItem="True"
Classes="ContentListView" Classes="ContentListView"
Grid.Row="2" Grid.Row="2"
IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren.Value.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}" IsVisible="{Binding AppState.SelectedTab.Value.SelectedsChildren.Value.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}"
ItemsSource="{Binding AppState.SelectedTab^.SelectedsChildren.Value}" ItemsSource="{Binding AppState.SelectedTab.Value.SelectedsChildren.Value}"
x:Name="ChildItems"> x:Name="ChildItems">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel"> <DataTemplate x:DataType="corevm:IItemViewModel">
@@ -547,14 +547,14 @@
FontWeight="Bold" FontWeight="Bold"
Foreground="{DynamicResource ErrorBrush}" Foreground="{DynamicResource ErrorBrush}"
HorizontalAlignment="Center" 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" Margin="10"
x:Name="ChildEmpty"> x:Name="ChildEmpty">
Empty Empty
</TextBlock> </TextBlock>
</Grid> </Grid>
<Grid IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren.Value, Converter={x:Static ObjectConverters.IsNull}, ConverterParameter=0, FallbackValue=False}" RowDefinitions="Auto, Auto"> <Grid IsVisible="{Binding AppState.SelectedTab.Value.SelectedsChildren.Value, Converter={x:Static ObjectConverters.IsNull}, ConverterParameter=0, FallbackValue=False}" RowDefinitions="Auto, Auto">
<TextBlock <TextBlock
Foreground="{DynamicResource ErrorBrush}" Foreground="{DynamicResource ErrorBrush}"
HorizontalAlignment="Center" HorizontalAlignment="Center"
@@ -562,7 +562,7 @@
Text="There were some errors while opening container." Text="There were some errors while opening container."
TextWrapping="Wrap" /> TextWrapping="Wrap" />
<ItemsRepeater Grid.Row="1" ItemsSource="{Binding AppState.SelectedTab^.CurrentSelectedItem.Value.BaseItem.Exceptions}"> <ItemsRepeater Grid.Row="1" ItemsSource="{Binding AppState.SelectedTab.Value.CurrentSelectedItem.Value.BaseItem.Exceptions}">
<ItemsRepeater.ItemTemplate> <ItemsRepeater.ItemTemplate>
<DataTemplate> <DataTemplate>
<TextBlock <TextBlock
@@ -574,8 +574,9 @@
</ItemsRepeater> </ItemsRepeater>
</Grid> </Grid>
</Grid> </Grid>
<Grid DataContext="{Binding ItemPreviewService.ItemPreview^}" IsVisible="{Binding Converter={x:Static ObjectConverters.IsNotNull}}"> <Grid DataContext="{Binding ItemPreviewService.ItemPreview.Value}" IsVisible="{Binding FallbackValue=false, Converter={x:Static ObjectConverters.IsNotNull}}">
<Grid IsVisible="{Binding Name, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static itemPreview:ElementPreviewViewModel.PreviewName}}" x:DataType="itemPreview:ElementPreviewViewModel"> <Grid IsVisible="{Binding Name, FallbackValue=false, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static itemPreview:ElementPreviewViewModel.PreviewName}}">
<Grid x:DataType="itemPreview:ElementPreviewViewModel">
<TextBlock <TextBlock
HorizontalAlignment="Center" HorizontalAlignment="Center"
IsVisible="{Binding Mode, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Unknown}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}" IsVisible="{Binding Mode, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Unknown}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}"
@@ -594,11 +595,10 @@
Text="{Binding TextEncoding, StringFormat=Encoding: {0}}" /> Text="{Binding TextEncoding, StringFormat=Encoding: {0}}" />
</Grid> </Grid>
</Grid> </Grid>
<Grid </Grid>
IsVisible="{Binding Name, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static sizePreview:ContainerSizeContainerPreview.PreviewName}}" <Grid IsVisible="{Binding Name, FallbackValue=false, Converter={StaticResource EqualsConverter}, ConverterParameter={x:Static sizePreview:ContainerPreview.PreviewName}}" x:Name="SizeContainerPreview">
RowDefinitions="Auto, Auto" <Grid RowDefinitions="Auto, Auto" x:DataType="sizePreview:ContainerPreview">
x:DataType="sizePreview:ContainerSizeContainerPreview"> <ItemsControl ItemsSource="{Binding TopItems}" Margin="0,0,0,30">
<ItemsControl Items="{Binding TopItems^}" Margin="0,0,0,30">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" /> <StackPanel Orientation="Horizontal" />
@@ -611,7 +611,7 @@
<MultiBinding Converter="{StaticResource ItemSizeToSizeConverter}"> <MultiBinding Converter="{StaticResource ItemSizeToSizeConverter}">
<MultiBinding.Bindings> <MultiBinding.Bindings>
<Binding /> <Binding />
<Binding ElementName="SizeContainerPreview" Path="DataContext.TopItems^" /> <Binding ElementName="SizeContainerPreview" Path="DataContext" />
<Binding ElementName="SizeContainerPreview" Path="Bounds.Width" /> <Binding ElementName="SizeContainerPreview" Path="Bounds.Width" />
</MultiBinding.Bindings> </MultiBinding.Bindings>
</MultiBinding> </MultiBinding>
@@ -622,7 +622,7 @@
<MultiBinding Converter="{StaticResource ItemSizeToBrushConverter}"> <MultiBinding Converter="{StaticResource ItemSizeToBrushConverter}">
<MultiBinding.Bindings> <MultiBinding.Bindings>
<Binding /> <Binding />
<Binding ElementName="SizeContainerPreview" Path="DataContext.Items^" /> <Binding ElementName="SizeContainerPreview" Path="DataContext" />
</MultiBinding.Bindings> </MultiBinding.Bindings>
</MultiBinding> </MultiBinding>
</Rectangle.Fill> </Rectangle.Fill>
@@ -630,26 +630,28 @@
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Center"> <StackPanel HorizontalAlignment="Left" VerticalAlignment="Center">
<TextBlock <TextBlock
Classes="SmallText" Classes="SmallText"
Margin="0,5,0,2" FontWeight="Bold"
Margin="5,5,5,2"
Text="{Binding Name}"> Text="{Binding Name}">
<TextBlock.Foreground> <TextBlock.Foreground>
<MultiBinding Converter="{StaticResource ItemSizeToForegroundBrushConverter}"> <MultiBinding Converter="{StaticResource ItemSizeToForegroundBrushConverter}">
<MultiBinding.Bindings> <MultiBinding.Bindings>
<Binding /> <Binding />
<Binding ElementName="SizeContainerPreview" Path="DataContext.Items^" /> <Binding ElementName="SizeContainerPreview" Path="DataContext" />
</MultiBinding.Bindings> </MultiBinding.Bindings>
</MultiBinding> </MultiBinding>
</TextBlock.Foreground> </TextBlock.Foreground>
</TextBlock> </TextBlock>
<TextBlock <TextBlock
Classes="SmallText" Classes="SmallText"
Margin="0,2,0,5" FontWeight="Bold"
Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"> Margin="5,2,5,5"
Text="{Binding Size.Value, Converter={StaticResource FormatSizeConverter}}">
<TextBlock.Foreground> <TextBlock.Foreground>
<MultiBinding Converter="{StaticResource ItemSizeToForegroundBrushConverter}"> <MultiBinding Converter="{StaticResource ItemSizeToForegroundBrushConverter}">
<MultiBinding.Bindings> <MultiBinding.Bindings>
<Binding /> <Binding />
<Binding ElementName="SizeContainerPreview" Path="DataContext.Items^" /> <Binding ElementName="SizeContainerPreview" Path="DataContext" />
</MultiBinding.Bindings> </MultiBinding.Bindings>
</MultiBinding> </MultiBinding>
</TextBlock.Foreground> </TextBlock.Foreground>
@@ -660,10 +662,7 @@
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<ItemsControl <ItemsControl Grid.Row="1" ItemsSource="{Binding TopItems}">
Grid.Row="1"
Items="{Binding Items^}"
x:CompileBindings="False">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid ColumnDefinitions="Auto,*" Margin="0,0,0,20"> <Grid ColumnDefinitions="Auto,*" Margin="0,0,0,20">
@@ -672,7 +671,7 @@
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Right" HorizontalAlignment="Right"
Margin="0,0,20,0" Margin="0,0,20,0"
Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}" /> Text="{Binding Size.Value, Converter={StaticResource FormatSizeConverter}}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
@@ -681,10 +680,11 @@
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
</Grid>
<ItemsRepeater <ItemsRepeater
HorizontalAlignment="Center" HorizontalAlignment="Center"
IsVisible="{Binding AppState.PopupTexts.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}" IsVisible="{Binding AppState.PopupTexts.Count, Converter={StaticResource NotEqualsConverter}}"
ItemsSource="{Binding AppState.PopupTexts}" ItemsSource="{Binding AppState.PopupTexts}"
Margin="0,0,0,20" Margin="0,0,0,20"
VerticalAlignment="Top"> VerticalAlignment="Top">
@@ -718,7 +718,11 @@
</ItemsRepeater> </ItemsRepeater>
</Grid> </Grid>
<Grid Grid.Row="3"> <Grid Grid.Row="3" RowDefinitions="Auto, Auto">
<Grid>
<TextBlock IsVisible="{Binding AppState.ContainerStatus.Value, Converter={x:Static ObjectConverters.IsNotNull}}" Text="{Binding AppState.ContainerStatus.Value}" />
</Grid>
<Grid Grid.Row="1">
<Grid IsVisible="{Binding AppState.ViewMode^, Converter={StaticResource EqualsConverter}, ConverterParameter=RapidTravel}" RowDefinitions="1,Auto"> <Grid IsVisible="{Binding AppState.ViewMode^, Converter={StaticResource EqualsConverter}, ConverterParameter=RapidTravel}" RowDefinitions="1,Auto">
<Rectangle <Rectangle
@@ -773,6 +777,7 @@
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
</Grid>
<Border <Border
Background="{DynamicResource BarelyTransparentBackgroundColor}" Background="{DynamicResource BarelyTransparentBackgroundColor}"

View File

@@ -38,3 +38,51 @@ public sealed class CombineLatestProperty<T1, T2, TResult> : DeclarativeProperty
await SetNewValueAsync(result, cancellationToken); await SetNewValueAsync(result, cancellationToken);
} }
} }
public sealed class CombineLatestProperty<T1, T2, T3, TResult> : DeclarativePropertyBase<TResult>
{
private readonly Func<T1?, T2?,T3?, Task<TResult>> _func;
private T1? _value1;
private T2? _value2;
private T3? _value3;
public CombineLatestProperty(
IDeclarativeProperty<T1> prop1,
IDeclarativeProperty<T2> prop2,
IDeclarativeProperty<T3> prop3,
Func<T1?, T2?, T3?,Task<TResult>> func,
Action<TResult?>? 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);
}
}

View File

@@ -8,6 +8,14 @@ public static class DeclarativePropertyHelpers
Func<T1, T2, Task<TResult>> func, Func<T1, T2, Task<TResult>> func,
Action<TResult?>? setValueHook = null) Action<TResult?>? setValueHook = null)
=> new(prop1, prop2, func, setValueHook); => new(prop1, prop2, func, setValueHook);
public static CombineLatestProperty<T1, T2, T3, TResult> CombineLatest<T1, T2, T3, TResult>(
IDeclarativeProperty<T1> prop1,
IDeclarativeProperty<T2> prop2,
IDeclarativeProperty<T3> prop3,
Func<T1, T2, T3, Task<TResult>> func,
Action<TResult?>? setValueHook = null)
=> new(prop1, prop2, prop3, func, setValueHook);
} }
public sealed class DeclarativeProperty<T> : DeclarativePropertyBase<T> public sealed class DeclarativeProperty<T> : DeclarativePropertyBase<T>