Element preview

This commit is contained in:
2022-05-18 21:38:46 +02:00
parent 255f3d34c9
commit 40f36ed845
22 changed files with 406 additions and 181 deletions

View File

@@ -0,0 +1,3 @@
namespace FileTime.App.Core.Models;
public record FileExtension(long? Size);

View File

@@ -0,0 +1,8 @@
namespace FileTime.App.Core.Models;
public enum ItemPreviewMode
{
Unknown,
Text,
Empty
}

View File

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

View File

@@ -1,9 +1,10 @@
using FileTime.App.Core.Models;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using InitableService; using InitableService;
namespace FileTime.App.Core.ViewModels; namespace FileTime.App.Core.ViewModels;
public interface IFileViewModel : IElementViewModel, IInitable<IFileElement, ITabViewModel, ItemViewModelType> public interface IFileViewModel : IElementViewModel, IInitable<IElement, FileExtension, ITabViewModel, ItemViewModelType>
{ {
} }

View File

@@ -0,0 +1,8 @@
using FileTime.App.Core.Models;
namespace FileTime.App.Core.ViewModels.ItemPreview;
public interface IItemPreviewViewModel
{
ItemPreviewMode Mode { get; }
}

View File

@@ -0,0 +1,40 @@
using System.Reactive.Linq;
using FileTime.App.Core.ViewModels;
using FileTime.App.Core.ViewModels.ItemPreview;
using FileTime.Core.Models;
using Microsoft.Extensions.DependencyInjection;
namespace FileTime.App.Core.Services;
public class ItemPreviewService : IItemPreviewService
{
private readonly IServiceProvider _serviceProvider;
public IObservable<IItemPreviewViewModel?> ItemPreview { get; }
public ItemPreviewService(IAppState appState, IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
ItemPreview = appState
.SelectedTab
.Select(t => t?.CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250)) ?? Observable.Return<IItemViewModel?>(null))
.Switch()
.Select(item =>
item == null
? Observable.Return<IItemPreviewViewModel?>(null)
: Observable.FromAsync(async () => await Map(item))
)
.Switch()
.Publish(null)
.RefCount();
}
private async Task<IItemPreviewViewModel?> Map(IItemViewModel itemViewModel)
{
return itemViewModel.BaseItem switch
{
IElement element => await _serviceProvider.GetAsyncInitableResolver(element)
.GetRequiredServiceAsync<ElementPreviewViewModel>(),
_ => null
};
}
}

View File

@@ -2,6 +2,7 @@ using FileTime.App.Core.Services;
using FileTime.App.Core.Services.UserCommandHandler; using FileTime.App.Core.Services.UserCommandHandler;
using FileTime.App.Core.StartupServices; using FileTime.App.Core.StartupServices;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.App.Core.ViewModels.ItemPreview;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace FileTime.App.Core; namespace FileTime.App.Core;
@@ -17,10 +18,12 @@ public static class Startup
.AddTransient<IFileViewModel, FileViewModel>() .AddTransient<IFileViewModel, FileViewModel>()
.AddTransient<IContainerSizeContainerViewModel, ContainerSizeContainerViewModel>() .AddTransient<IContainerSizeContainerViewModel, ContainerSizeContainerViewModel>()
.AddTransient<IItemNameConverterService, ItemNameConverterService>() .AddTransient<IItemNameConverterService, ItemNameConverterService>()
.AddTransient<ElementPreviewViewModel>()
.AddSingleton<IUserCommandHandlerService, UserCommandHandlerService>() .AddSingleton<IUserCommandHandlerService, UserCommandHandlerService>()
.AddSingleton<IClipboardService, ClipboardService>() .AddSingleton<IClipboardService, ClipboardService>()
.AddSingleton<IIdentifiableUserCommandService, IdentifiableUserCommandService>() .AddSingleton<IIdentifiableUserCommandService, IdentifiableUserCommandService>()
.AddSingleton<IStartupHandler, DefaultIdentifiableCommandHandlerRegister>() .AddSingleton<IStartupHandler, DefaultIdentifiableCommandHandlerRegister>()
.AddSingleton<IItemPreviewService, ItemPreviewService>()
.AddCommandHandlers(); .AddCommandHandlers();
} }

View File

@@ -1,3 +1,4 @@
using FileTime.App.Core.Models;
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;
@@ -12,8 +13,9 @@ public partial class FileViewModel : ElementViewModel, IFileViewModel
{ {
} }
public void Init(IFileElement item, ITabViewModel parentTab, ItemViewModelType itemViewModelType) public void Init(IElement item, FileExtension fileExtension, ITabViewModel parentTab, ItemViewModelType itemViewModelType)
{ {
Init((IElement)item, parentTab, itemViewModelType); Init((IElement)item, parentTab, itemViewModelType);
Size = fileExtension.Size;
} }
} }

View File

@@ -0,0 +1,39 @@
using System.Text;
using FileTime.App.Core.Models;
using FileTime.Core.Models;
using InitableService;
using MvvmGen;
namespace FileTime.App.Core.ViewModels.ItemPreview;
[ViewModel]
public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInitable<IElement>
{
private const int MaxTextPreviewSize = 1024 * 1024;
public ItemPreviewMode Mode { get; private set; }
[Property] private string? _textContent;
public async Task InitAsync(IElement element)
{
try
{
var content = await element.Provider.GetContentAsync(element, MaxTextPreviewSize);
TextContent = content is null
? "Could not read any data from file " + element.Name
: Encoding.UTF8.GetString(content);
}
catch(Exception ex)
{
TextContent = $"Error while getting content of {element.FullName}. " + ex.ToString();
}
Mode = (TextContent?.Length ?? 0) switch
{
0 => ItemPreviewMode.Empty,
_ => ItemPreviewMode.Text
};
}
}

View File

@@ -4,6 +4,7 @@ using FileTime.App.Core.Extensions;
using FileTime.App.Core.Models; using FileTime.App.Core.Models;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.Services; using FileTime.App.Core.Services;
using FileTime.App.Core.ViewModels.ItemPreview;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Services; using FileTime.Core.Services;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -34,9 +35,14 @@ public partial class TabViewModel : ITabViewModel, IDisposable
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> SelectedsChildren { get; private set; } = null!; public IObservable<IObservable<IChangeSet<IItemViewModel>>?> SelectedsChildren { get; private set; } = null!;
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> ParentsChildren { get; private set; } = null!; public IObservable<IObservable<IChangeSet<IItemViewModel>>?> ParentsChildren { get; private set; } = null!;
public IObservable<IReadOnlyCollection<IItemViewModel>?> CurrentItemsCollectionObservable { get; private set; } = null!; public IObservable<IReadOnlyCollection<IItemViewModel>?> CurrentItemsCollectionObservable { get; private set; } =
public IObservable<IReadOnlyCollection<IItemViewModel>?> ParentsChildrenCollectionObservable { get; private set; } = null!; null!;
public IObservable<IReadOnlyCollection<IItemViewModel>?> SelectedsChildrenCollectionObservable { get; private set; } = null!;
public IObservable<IReadOnlyCollection<IItemViewModel>?> ParentsChildrenCollectionObservable { get; private set; } =
null!;
public IObservable<IReadOnlyCollection<IItemViewModel>?>
SelectedsChildrenCollectionObservable { get; private set; } = null!;
[Property] private BindedCollection<IItemViewModel>? _currentItemsCollection; [Property] private BindedCollection<IItemViewModel>? _currentItemsCollection;
@@ -78,7 +84,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable
tab.CurrentSelectedItem, tab.CurrentSelectedItem,
(currentItems, currentSelectedItemPath) => (currentItems, currentSelectedItemPath) =>
currentItems == null currentItems == null
? Observable.Return((IItemViewModel?) null) ? Observable.Return((IItemViewModel?)null)
: currentItems : currentItems
.ToCollection() .ToCollection()
.Select(items => .Select(items =>
@@ -117,7 +123,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable
.Transform(i => MapItemToViewModel(i, ItemViewModelType.SelectedChild))), .Transform(i => MapItemToViewModel(i, ItemViewModelType.SelectedChild))),
currentSelectedItemThrottled currentSelectedItemThrottled
.Where(c => c is null or not IContainerViewModel) .Where(c => c is null or not IContainerViewModel)
.Select(_ => (IObservable<IChangeSet<IItemViewModel>>?) null) .Select(_ => (IObservable<IChangeSet<IItemViewModel>>?)null)
) )
/*.ObserveOn(_rxSchedulerService.GetWorkerScheduler()) /*.ObserveOn(_rxSchedulerService.GetWorkerScheduler())
.SubscribeOn(_rxSchedulerService.GetUIScheduler())*/ .SubscribeOn(_rxSchedulerService.GetUIScheduler())*/
@@ -136,7 +142,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable
return Observable.Merge( return Observable.Merge(
parentThrottled parentThrottled
.Where(p => p is not null) .Where(p => p is not null)
.Select(p => Observable.FromAsync(async () => (IContainer) await p!.ResolveAsync())) .Select(p => Observable.FromAsync(async () => (IContainer)await p!.ResolveAsync()))
.Switch() .Switch()
.Select(p => p.Items) .Select(p => p.Items)
.Switch() .Switch()
@@ -145,7 +151,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable
.Transform(i => MapItemToViewModel(i, ItemViewModelType.Parent))), .Transform(i => MapItemToViewModel(i, ItemViewModelType.Parent))),
parentThrottled parentThrottled
.Where(p => p is null) .Where(p => p is null)
.Select(_ => (IObservable<IChangeSet<IItemViewModel>>?) null) .Select(_ => (IObservable<IChangeSet<IItemViewModel>>?)null)
) )
/*.ObserveOn(_rxSchedulerService.GetWorkerScheduler()) /*.ObserveOn(_rxSchedulerService.GetWorkerScheduler())
.SubscribeOn(_rxSchedulerService.GetUIScheduler())*/ .SubscribeOn(_rxSchedulerService.GetUIScheduler())*/
@@ -158,7 +164,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable
{ {
return source return source
.Select(c => .Select(c =>
c != null ? c.ToCollection() : Observable.Return((IReadOnlyCollection<IItemViewModel>?) null)) c != null ? c.ToCollection() : Observable.Return((IReadOnlyCollection<IItemViewModel>?)null))
.Switch() .Switch()
.Publish(null) .Publish(null)
.RefCount(); .RefCount();
@@ -179,16 +185,20 @@ public partial class TabViewModel : ITabViewModel, IDisposable
return containerViewModel; return containerViewModel;
} }
else if (item is IFileElement fileElement) else if (item is IElement element)
{
var fileExtension = element.GetExtension<FileExtension>();
if (fileExtension is not null)
{ {
var fileViewModel = _serviceProvider var fileViewModel = _serviceProvider
.GetInitableResolver<IFileElement, ITabViewModel, ItemViewModelType>(fileElement, this, type) .GetInitableResolver<IElement, FileExtension, ITabViewModel, ItemViewModelType>(
element, fileExtension, this, type)
.GetRequiredService<IFileViewModel>(); .GetRequiredService<IFileViewModel>();
fileViewModel.Size = fileElement.Size;
return fileViewModel; return fileViewModel;
} }
else if (item is IElement element) else
{ {
var elementViewModel = _serviceProvider var elementViewModel = _serviceProvider
.GetInitableResolver<IElement, ITabViewModel, ItemViewModelType>(element, this, type) .GetInitableResolver<IElement, ITabViewModel, ItemViewModelType>(element, this, type)
@@ -196,6 +206,7 @@ public partial class TabViewModel : ITabViewModel, IDisposable
return elementViewModel; return elementViewModel;
} }
}
throw new ArgumentException($"{nameof(item)} is not {nameof(IContainer)} neither {nameof(IElement)}"); throw new ArgumentException($"{nameof(item)} is not {nameof(IContainer)} neither {nameof(IElement)}");
} }

View File

@@ -0,0 +1,47 @@
using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
namespace FileTime.Core.Models;
public class ExtensionCollection : IEnumerable<object>
{
private readonly List<object> _extensions = new();
public ExtensionCollection()
{
}
public ExtensionCollection(IEnumerable<object> objects)
{
foreach (var obj in objects)
{
AddSafe(obj);
}
}
private void AddSafe(object obj)
{
var objType = obj.GetType();
if (_extensions.Any(i => i.GetType() == objType))
throw new ArgumentException($"Collection already contains an item with type {objType.FullName}");
_extensions.Add(obj);
}
public void Add<T>([DisallowNull] T obj, [CallerArgumentExpression("obj")] string? paramName = null)
{
ArgumentNullException.ThrowIfNull(obj, paramName);
AddSafe(obj);
}
public void Remove<T>()
{
_extensions.RemoveAll(i => i is T);
}
public ReadOnlyExtensionCollection AsReadOnly() => new ReadOnlyExtensionCollection(this);
public IEnumerator<object> GetEnumerator() => _extensions.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _extensions.GetEnumerator();
}

View File

@@ -1,6 +0,0 @@
namespace FileTime.Core.Models;
public interface IFileElement : IElement
{
long Size { get; }
}

View File

@@ -1,3 +1,4 @@
using System.Reactive.Linq;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Services; using FileTime.Core.Services;
@@ -19,4 +20,7 @@ public interface IItem
string? Attributes { get; } string? Attributes { get; }
AbsolutePathType Type { get; } AbsolutePathType Type { get; }
IObservable<IEnumerable<Exception>> Exceptions { get; } IObservable<IEnumerable<Exception>> Exceptions { get; }
ReadOnlyExtensionCollection Extensions { get; }
T? GetExtension<T>() => (T?)Extensions.FirstOrDefault(i => i is T);
} }

View File

@@ -0,0 +1,17 @@
using System.Collections;
namespace FileTime.Core.Models;
public class ReadOnlyExtensionCollection : IEnumerable<object>
{
private readonly ExtensionCollection _collection;
public ReadOnlyExtensionCollection(ExtensionCollection collection)
{
_collection = collection;
}
public IEnumerator<object> GetEnumerator() => _collection.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => _collection.GetEnumerator();
}

View File

@@ -20,4 +20,6 @@ public interface IContentProvider : IContainer, IOnContainerEnter
Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName); Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName);
NativePath GetNativePath(FullName fullName); NativePath GetNativePath(FullName fullName);
Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null, CancellationToken cancellationToken = default);
} }

View File

@@ -1,3 +1,4 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using DynamicData; using DynamicData;
@@ -20,6 +21,7 @@ public record Container(
string? Attributes, string? Attributes,
IContentProvider Provider, IContentProvider Provider,
IObservable<IEnumerable<Exception>> Exceptions, IObservable<IEnumerable<Exception>> Exceptions,
ReadOnlyExtensionCollection Extensions,
IObservable<IObservable<IChangeSet<IAbsolutePath>>?> Items) : IContainer IObservable<IObservable<IChangeSet<IAbsolutePath>>?> Items) : IContainer
{ {
BehaviorSubject<bool> IsLoading { get; } = new BehaviorSubject<bool>(false); BehaviorSubject<bool> IsLoading { get; } = new BehaviorSubject<bool>(false);

View File

@@ -1,3 +1,4 @@
using System.Collections.ObjectModel;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Services; using FileTime.Core.Services;
@@ -16,7 +17,8 @@ public record Element(
bool CanRename, bool CanRename,
string? Attributes, string? Attributes,
IContentProvider Provider, IContentProvider Provider,
IObservable<IEnumerable<Exception>> Exceptions) : IElement IObservable<IEnumerable<Exception>> Exceptions,
ReadOnlyExtensionCollection Extensions) : IElement
{ {
public AbsolutePathType Type => AbsolutePathType.Element; public AbsolutePathType Type => AbsolutePathType.Element;
} }

View File

@@ -1,35 +0,0 @@
using FileTime.Core.Enums;
using FileTime.Core.Services;
namespace FileTime.Core.Models;
public record FileElement(
string Name,
string DisplayName,
FullName FullName,
NativePath NativePath,
IAbsolutePath? Parent,
bool IsHidden,
bool IsExists,
DateTime? CreatedAt,
SupportsDelete CanDelete,
bool CanRename,
string? Attributes,
IContentProvider Provider,
IObservable<IEnumerable<Exception>> Exceptions,
long Size)
: Element(
Name,
DisplayName,
FullName,
NativePath,
Parent,
IsHidden,
IsExists,
CreatedAt,
CanDelete,
CanRename,
Attributes,
Provider,
Exceptions
), IFileElement;

View File

@@ -1,3 +1,4 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using DynamicData; using DynamicData;
@@ -8,7 +9,10 @@ namespace FileTime.Core.Services;
public abstract class ContentProviderBase : IContentProvider public abstract class ContentProviderBase : IContentProvider
{ {
private readonly ReadOnlyExtensionCollection _extensions;
protected BehaviorSubject<IObservable<IChangeSet<IAbsolutePath>>?> Items { get; } = new(null); protected BehaviorSubject<IObservable<IChangeSet<IAbsolutePath>>?> Items { get; } = new(null);
protected ExtensionCollection Extensions { get; }
IObservable<IObservable<IChangeSet<IAbsolutePath>>?> IContainer.Items => Items; IObservable<IObservable<IChangeSet<IAbsolutePath>>?> IContainer.Items => Items;
@@ -44,10 +48,14 @@ public abstract class ContentProviderBase : IContentProvider
public IObservable<IEnumerable<Exception>> Exceptions => Observable.Return(Enumerable.Empty<Exception>()); public IObservable<IEnumerable<Exception>> Exceptions => Observable.Return(Enumerable.Empty<Exception>());
ReadOnlyExtensionCollection IItem.Extensions => _extensions;
protected ContentProviderBase(string name) protected ContentProviderBase(string name)
{ {
DisplayName = Name = name; DisplayName = Name = name;
FullName = new FullName(name); FullName = new FullName(name);
Extensions = new ExtensionCollection();
_extensions = Extensions.AsReadOnly();
} }
public virtual Task OnEnter() => Task.CompletedTask; public virtual Task OnEnter() => Task.CompletedTask;
@@ -57,7 +65,8 @@ public abstract class ContentProviderBase : IContentProvider
bool forceResolve = false, bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default) ItemInitializationSettings itemInitializationSettings = default)
=> await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType, itemInitializationSettings); => await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType,
itemInitializationSettings);
public abstract Task<IItem> GetItemByNativePathAsync( public abstract Task<IItem> GetItemByNativePathAsync(
NativePath nativePath, NativePath nativePath,
@@ -67,4 +76,8 @@ public abstract class ContentProviderBase : IContentProvider
public abstract Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName); public abstract Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName);
public abstract NativePath GetNativePath(FullName fullName); public abstract NativePath GetNativePath(FullName fullName);
public abstract Task<byte[]?> GetContentAsync(IElement element,
int? maxLength = null,
CancellationToken cancellationToken = default);
} }

View File

@@ -20,6 +20,7 @@ namespace FileTime.GuiApp.ViewModels;
[Inject(typeof(IKeyInputHandlerService), PropertyName = "_keyInputHandlerService")] [Inject(typeof(IKeyInputHandlerService), PropertyName = "_keyInputHandlerService")]
[Inject(typeof(IUserCommandHandlerService), PropertyAccessModifier = AccessModifier.Public)] [Inject(typeof(IUserCommandHandlerService), PropertyAccessModifier = AccessModifier.Public)]
[Inject(typeof(LifecycleService), PropertyName = "_lifecycleService")] [Inject(typeof(LifecycleService), PropertyName = "_lifecycleService")]
[Inject(typeof(IItemPreviewService), PropertyAccessModifier = AccessModifier.Public)]
public partial class MainWindowViewModel : IMainWindowViewModelBase public partial class MainWindowViewModel : IMainWindowViewModelBase
{ {
public bool Loading => false; public bool Loading => false;

View File

@@ -8,6 +8,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FileTime.GuiApp.ViewModels" xmlns:vm="using:FileTime.GuiApp.ViewModels"
xmlns:config="using:FileTime.GuiApp.Configuration" xmlns:config="using:FileTime.GuiApp.Configuration"
xmlns:appCoreModels="using:FileTime.App.Core.Models"
Title="FileTime" Title="FileTime"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
@@ -44,7 +45,8 @@
<StackPanel <StackPanel
Margin="20,10" Margin="20,10"
Orientation="Horizontal"> Orientation="Horizontal">
<local:PathPresenter DataContext="{Binding AppState.SelectedTab^.CurrentLocation^.FullName.Path,Converter={StaticResource PathPreformatter}}"/> <local:PathPresenter
DataContext="{Binding AppState.SelectedTab^.CurrentLocation^.FullName.Path,Converter={StaticResource PathPreformatter}}" />
<TextBlock <TextBlock
Foreground="{StaticResource AccentBrush}" Foreground="{StaticResource AccentBrush}"
Text="{Binding AppState.SelectedTab^.CurrentSelectedItem^.DisplayNameText}" /> Text="{Binding AppState.SelectedTab^.CurrentSelectedItem^.DisplayNameText}" />
@@ -53,7 +55,8 @@
<Grid Grid.Row="1" RowDefinitions="Auto,Auto,Auto,Auto"> <Grid Grid.Row="1" RowDefinitions="Auto,Auto,Auto,Auto">
<Border CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}" Padding="10" Margin="10"> <Border CornerRadius="10" Background="{DynamicResource ContainerBackgroundBrush}" Padding="10"
Margin="10">
<Grid RowDefinitions="Auto,Auto"> <Grid RowDefinitions="Auto,Auto">
<TextBlock <TextBlock
@@ -65,8 +68,10 @@
Items="{Binding AppState.RootDriveInfos.Collection}"> Items="{Binding AppState.RootDriveInfos.Collection}">
<ItemsRepeater.ItemTemplate> <ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="vm:RootDriveInfo"> <DataTemplate x:DataType="vm:RootDriveInfo">
<Grid Classes="SidebarContainerPresenter" PointerPressed="OnHasContainerPointerPressed" Cursor="Hand"> <Grid Classes="SidebarContainerPresenter"
<Grid Margin="0,5" ColumnDefinitions="Auto,*,Auto" RowDefinitions="Auto,Auto"> PointerPressed="OnHasContainerPointerPressed" Cursor="Hand">
<Grid Margin="0,5" ColumnDefinitions="Auto,*,Auto"
RowDefinitions="Auto,Auto">
<Image <Image
Grid.RowSpan="2" Grid.RowSpan="2"
Width="20" Width="20"
@@ -90,7 +95,8 @@
Margin="5,0,0,0" Margin="5,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Classes="ExtraSmallText" Classes="ExtraSmallText"
Text="{Binding Label}" IsVisible="{Binding Label,Converter={x:Static StringConverters.IsNotNullOrEmpty}}" /> Text="{Binding Label}"
IsVisible="{Binding Label,Converter={x:Static StringConverters.IsNotNullOrEmpty}}" />
</StackPanel> </StackPanel>
<StackPanel <StackPanel
@@ -99,13 +105,16 @@
Orientation="Horizontal" Orientation="Horizontal"
VerticalAlignment="Center"> VerticalAlignment="Center">
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"> <TextBlock Classes="SmallText" VerticalAlignment="Center"
Text="{Binding Free, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
</TextBlock> </TextBlock>
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text=" / "> <TextBlock Classes="SmallText" VerticalAlignment="Center"
Text=" / ">
</TextBlock> </TextBlock>
<TextBlock Classes="SmallText" VerticalAlignment="Center" Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}"> <TextBlock Classes="SmallText" VerticalAlignment="Center"
Text="{Binding Size, Converter={StaticResource FormatSizeConverter}, ConverterParameter=0}">
</TextBlock> </TextBlock>
</StackPanel> </StackPanel>
@@ -352,6 +361,8 @@
Fill="{DynamicResource ContentSeparatorBrush}" /> Fill="{DynamicResource ContentSeparatorBrush}" />
<Grid Grid.Column="4"> <Grid Grid.Column="4">
<Grid
IsVisible="{Binding ItemPreviewService.ItemPreview^,Converter={x:Static ObjectConverters.IsNull}}">
<Grid <Grid
IsVisible="{Binding AppState.SelectedTab^.SelectedsChildrenCollection.Collection, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}"> IsVisible="{Binding AppState.SelectedTab^.SelectedsChildrenCollection.Collection, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}">
<ListBox <ListBox
@@ -403,6 +414,18 @@
</ItemsRepeater> </ItemsRepeater>
</Grid> </Grid>
</Grid> </Grid>
<Grid
IsVisible="{Binding ItemPreviewService.ItemPreview^,Converter={x:Static ObjectConverters.IsNotNull}}">
<TextBlock HorizontalAlignment="Center" Text="Don't know how to preview this item." IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Unknown}}"/>
<TextBlock HorizontalAlignment="Center" Text="Empty" IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Empty}}"/>
<ScrollViewer IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Text}}">
<TextBox
IsReadOnly="True"
x:CompileBindings="False"
Text="{Binding ItemPreviewService.ItemPreview^.TextContent}" />
</ScrollViewer>
</Grid>
</Grid>
</Grid> </Grid>
</Grid> </Grid>

View File

@@ -2,6 +2,7 @@ using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using DynamicData; using DynamicData;
using FileTime.App.Core.Models;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Services; using FileTime.Core.Services;
@@ -133,6 +134,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
"???", "???",
this, this,
nonNullExceptions, nonNullExceptions,
new ExtensionCollection().AsReadOnly(),
Observable.Return<IObservable<IChangeSet<IAbsolutePath>>?>(null) Observable.Return<IObservable<IChangeSet<IAbsolutePath>>?>(null)
); );
} }
@@ -195,6 +197,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
GetDirectoryAttributes(directoryInfo), GetDirectoryAttributes(directoryInfo),
this, this,
exceptions, exceptions,
new ExtensionCollection().AsReadOnly(),
Observable.FromAsync(async () => await Task.Run(InitChildren)) Observable.FromAsync(async () => await Task.Run(InitChildren))
); );
@@ -226,7 +229,12 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
throw new Exception($"Path does not have parent: '{fileInfo.FullName}'"); throw new Exception($"Path does not have parent: '{fileInfo.FullName}'");
var parent = new AbsolutePath(this, parentFullName, AbsolutePathType.Container); var parent = new AbsolutePath(this, parentFullName, AbsolutePathType.Container);
return new FileElement( var extensions = new ExtensionCollection()
{
new FileExtension(fileInfo.Length)
};
return new Element(
fileInfo.Name, fileInfo.Name,
fileInfo.Name, fileInfo.Name,
fullName, fullName,
@@ -240,7 +248,7 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
GetFileAttributes(fileInfo), GetFileAttributes(fileInfo),
this, this,
Observable.Return(Enumerable.Empty<Exception>()), Observable.Return(Enumerable.Empty<Exception>()),
fileInfo.Length extensions.AsReadOnly()
); );
} }
@@ -261,4 +269,28 @@ public sealed partial class LocalContentProvider : ContentProviderBase, ILocalCo
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !path.StartsWith("/")) path = "/" + path; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !path.StartsWith("/")) path = "/" + path;
return new NativePath(path); return new NativePath(path);
} }
public override async Task<byte[]?> GetContentAsync(IElement element, int? maxLength = null,
CancellationToken cancellationToken = default)
{
if (cancellationToken.IsCancellationRequested) return null;
if (!File.Exists(element.NativePath!.Path))
throw new FileNotFoundException("File does not exist", element.NativePath.Path);
await using var reader = new FileStream(element.NativePath!.Path, FileMode.Open, FileAccess.Read, FileShare.Read,
bufferSize: 1, // bufferSize == 1 used to avoid unnecessary buffer in FileStream
FileOptions.Asynchronous | FileOptions.SequentialScan);
var realFileSize = new FileInfo(element.NativePath!.Path).Length;
var size = maxLength ?? realFileSize switch
{
> int.MaxValue => int.MaxValue,
_ => (int)realFileSize
};
var buffer = new byte[size];
await reader.ReadAsync(buffer, 0, size);
return buffer;
}
} }