Show children

This commit is contained in:
2022-04-15 00:13:22 +02:00
parent efe2090c6d
commit 84522d8f96
21 changed files with 591 additions and 366 deletions

View File

@@ -12,7 +12,8 @@ namespace FileTime.App.Core.ViewModels
IObservable<bool> IsSelected { get; } IObservable<bool> IsSelected { get; }
IObservable<IContainer?> CurrentLocation { get; } IObservable<IContainer?> CurrentLocation { get; }
IObservable<IItemViewModel?> CurrentSelectedItem { get; } IObservable<IItemViewModel?> CurrentSelectedItem { get; }
IObservable<IEnumerable<IItemViewModel>> CurrentItems { get; } IObservable<IReadOnlyList<IItemViewModel>> CurrentItems { get; }
IObservable<IEnumerable<FullName>> MarkedItems { get; } IObservable<IEnumerable<FullName>> MarkedItems { get; }
IObservable<IReadOnlyList<IItemViewModel>?> SelectedsChildren { get; }
} }
} }

View File

@@ -0,0 +1,9 @@
using System.Reactive.Linq;
namespace FileTime.App.Core.Extensions
{
public static class ObservableExtensions
{
public static IObservable<T> WhereNotNull<T>(this IObservable<T?> source) => source.Where(c => c != null)!;
}
}

View File

@@ -9,6 +9,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="morelinq" Version="3.3.2" /> <PackageReference Include="morelinq" Version="3.3.2" />
<PackageReference Include="MvvmGen" Version="1.1.5" /> <PackageReference Include="MvvmGen" Version="1.1.5" />
<PackageReference Include="System.Interactive.Async" Version="6.0.1" />
<PackageReference Include="System.Reactive" Version="5.0.0" /> <PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup> </ItemGroup>

View File

@@ -100,7 +100,7 @@ namespace FileTime.App.Core.Services
private async Task GoUp() private async Task GoUp()
{ {
if (_currentLocation?.Parent is not IAbsolutePath parentPath || await parentPath.ResolveAsync() is not IContainer newContainer) return; if (_currentLocation?.Parent is not IAbsolutePath parentPath || await parentPath.ResolveAsyncSafe() is not IContainer newContainer) return;
_selectedTab?.Tab?.SetCurrentLocation(newContainer); _selectedTab?.Tab?.SetCurrentLocation(newContainer);
} }

View File

@@ -1,7 +1,9 @@
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using FileTime.App.Core.Extensions;
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.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Services; using FileTime.Core.Services;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -24,8 +26,9 @@ namespace FileTime.App.Core.ViewModels
public IObservable<IContainer?> CurrentLocation { get; private set; } = null!; public IObservable<IContainer?> CurrentLocation { get; private set; } = null!;
public IObservable<IItemViewModel?> CurrentSelectedItem { get; private set; } = null!; public IObservable<IItemViewModel?> CurrentSelectedItem { get; private set; } = null!;
public IObservable<IEnumerable<IItemViewModel>> CurrentItems { get; private set; } = null!; public IObservable<IReadOnlyList<IItemViewModel>> CurrentItems { get; private set; } = null!;
public IObservable<IEnumerable<FullName>> MarkedItems { get; } public IObservable<IEnumerable<FullName>> MarkedItems { get; }
public IObservable<IReadOnlyList<IItemViewModel>?> SelectedsChildren { get; private set; } = null!;
public TabViewModel( public TabViewModel(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
@@ -46,7 +49,7 @@ namespace FileTime.App.Core.ViewModels
TabNumber = tabNumber; TabNumber = tabNumber;
CurrentLocation = tab.CurrentLocation.AsObservable(); CurrentLocation = tab.CurrentLocation.AsObservable();
CurrentItems = tab.CurrentItems.Select(items => items.Select(MapItemToViewModel).ToList()).Publish(Enumerable.Empty<IItemViewModel>()).RefCount(); CurrentItems = tab.CurrentItems.Select(items => items.Select(MapItemToViewModel).ToList()).Publish(new List<IItemViewModel>()).RefCount();
CurrentSelectedItem = CurrentSelectedItem =
Observable.CombineLatest( Observable.CombineLatest(
CurrentItems, CurrentItems,
@@ -55,7 +58,34 @@ namespace FileTime.App.Core.ViewModels
) )
.Publish(null) .Publish(null)
.RefCount(); .RefCount();
var currentSelectedItemThrottled = CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250)).Publish(null).RefCount();
SelectedsChildren = Observable.Merge(
currentSelectedItemThrottled
.WhereNotNull()
.OfType<IContainerViewModel>()
.Where(c => c?.Container is not null)
.Select(c => c.Container!.Items)
.Switch()
.Select(items => Observable.FromAsync(async () => await Map(items)))
.Switch()
.Select(items => items?.Select(MapItemToViewModel).ToList()),
currentSelectedItemThrottled
.Where(c => c is null || c is not IContainerViewModel)
.Select(_ => (IReadOnlyList<IItemViewModel>?)null)
);
tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty<FullName>())); tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty<FullName>()));
static async Task<List<IItem>?> Map(IEnumerable<IAbsolutePath>? items)
{
if (items == null) return null;
return await items
.ToAsyncEnumerable()
.SelectAwait(async i => await i.ResolveAsync(true))
.ToListAsync();
}
} }
private IItemViewModel MapItemToViewModel(IItem item, int index) private IItemViewModel MapItemToViewModel(IItem item, int index)

View File

@@ -10,6 +10,7 @@ namespace FileTime.Core.Models
FullName Path { get; } FullName Path { get; }
AbsolutePathType Type { get; } AbsolutePathType Type { get; }
Task<IItem> ResolveAsync(); Task<IItem> ResolveAsync(bool forceResolve = false);
Task<IItem?> ResolveAsyncSafe(bool forceResolve = false);
} }
} }

View File

@@ -2,7 +2,7 @@ namespace FileTime.Core.Models
{ {
public interface IContainer : IItem public interface IContainer : IItem
{ {
IObservable<IReadOnlyList<IAbsolutePath>> Items { get; } IObservable<IEnumerable<IAbsolutePath>?> Items { get; }
IObservable<bool> IsLoading { get; } IObservable<bool> IsLoading { get; }
} }
} }

View File

@@ -18,5 +18,6 @@ namespace FileTime.Core.Models
IContentProvider Provider { get; } IContentProvider Provider { get; }
string? Attributes { get; } string? Attributes { get; }
AbsolutePathType Type { get; } AbsolutePathType Type { get; }
IObservable<IEnumerable<Exception>> Exceptions { get; }
} }
} }

View File

@@ -1,12 +1,13 @@
using FileTime.Core.Behaviors; using FileTime.Core.Behaviors;
using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.Core.Services namespace FileTime.Core.Services
{ {
public interface IContentProvider : IContainer, IOnContainerEnter public interface IContentProvider : IContainer, IOnContainerEnter
{ {
Task<IItem> GetItemByFullNameAsync(FullName fullName); Task<IItem> GetItemByFullNameAsync(FullName fullName, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown);
Task<IItem> GetItemByNativePathAsync(NativePath nativePath); Task<IItem> GetItemByNativePathAsync(NativePath nativePath, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown);
Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName); Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName);
} }
} }

View File

@@ -1,6 +1,5 @@
using FileTime.Core.Models; using FileTime.Core.Models;
using InitableService; using InitableService;
using System.Reactive.Subjects;
namespace FileTime.Core.Services namespace FileTime.Core.Services
{ {
@@ -8,7 +7,7 @@ namespace FileTime.Core.Services
{ {
IObservable<IContainer?> CurrentLocation { get; } IObservable<IContainer?> CurrentLocation { get; }
IObservable<IAbsolutePath?> CurrentSelectedItem { get; } IObservable<IAbsolutePath?> CurrentSelectedItem { get; }
IObservable<IEnumerable<IItem>> CurrentItems { get; } IObservable<IEnumerable<IItem>?> CurrentItems { get; }
void SetCurrentLocation(IContainer newLocation); void SetCurrentLocation(IContainer newLocation);
void AddSelectedItemsTransformator(ItemsTransformator transformator); void AddSelectedItemsTransformator(ItemsTransformator transformator);

View File

@@ -19,10 +19,19 @@ namespace FileTime.Core.Models
Type = type; Type = type;
} }
public async Task<IItem> ResolveAsync() public async Task<IItem> ResolveAsync(bool forceResolve = false)
{ {
var provider = VirtualContentProvider ?? ContentProvider; var provider = VirtualContentProvider ?? ContentProvider;
return await provider.GetItemByFullNameAsync(Path); return await provider.GetItemByFullNameAsync(Path, forceResolve, Type);
}
public async Task<IItem?> ResolveAsyncSafe(bool forceResolve = false)
{
try
{
return await ResolveAsync(forceResolve);
}
catch { return null; }
} }
} }
} }

View File

@@ -18,7 +18,8 @@ namespace FileTime.Core.Models
bool CanRename, bool CanRename,
string? Attributes, string? Attributes,
IContentProvider Provider, IContentProvider Provider,
IObservable<IReadOnlyList<IAbsolutePath>> Items) : IContainer IObservable<IEnumerable<Exception>> Exceptions,
IObservable<IEnumerable<IAbsolutePath>?> Items) : IContainer
{ {
BehaviorSubject<bool> IsLoading { get; } = new BehaviorSubject<bool>(false); BehaviorSubject<bool> IsLoading { get; } = new BehaviorSubject<bool>(false);
IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable(); IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable();

View File

@@ -15,7 +15,8 @@ namespace FileTime.Core.Models
SupportsDelete CanDelete, SupportsDelete CanDelete,
bool CanRename, bool CanRename,
string? Attributes, string? Attributes,
IContentProvider Provider) : IElement IContentProvider Provider,
IObservable<IEnumerable<Exception>> Exceptions) : IElement
{ {
public AbsolutePathType Type => AbsolutePathType.Element; public AbsolutePathType Type => AbsolutePathType.Element;
} }

View File

@@ -16,6 +16,7 @@ namespace FileTime.Core.Models
bool CanRename, bool CanRename,
string? Attributes, string? Attributes,
IContentProvider Provider, IContentProvider Provider,
IObservable<IEnumerable<Exception>> Exceptions,
long Size) long Size)
: Element( : Element(
Name, Name,
@@ -29,6 +30,7 @@ namespace FileTime.Core.Models
CanDelete, CanDelete,
CanRename, CanRename,
Attributes, Attributes,
Provider Provider,
Exceptions
), IFileElement; ), IFileElement;
} }

View File

@@ -9,7 +9,7 @@ namespace FileTime.Core.Services
{ {
protected BehaviorSubject<IReadOnlyList<IAbsolutePath>> Items { get; } = new BehaviorSubject<IReadOnlyList<IAbsolutePath>>(new List<IAbsolutePath>()); protected BehaviorSubject<IReadOnlyList<IAbsolutePath>> Items { get; } = new BehaviorSubject<IReadOnlyList<IAbsolutePath>>(new List<IAbsolutePath>());
IObservable<IReadOnlyList<IAbsolutePath>> IContainer.Items => Items; IObservable<IEnumerable<IAbsolutePath>> IContainer.Items => Items;
public string Name { get; } public string Name { get; }
@@ -41,14 +41,17 @@ namespace FileTime.Core.Services
public AbsolutePathType Type => AbsolutePathType.Container; public AbsolutePathType Type => AbsolutePathType.Container;
public IObservable<IEnumerable<Exception>> Exceptions => Observable.Return(Enumerable.Empty<Exception>());
protected ContentProviderBase(string name) protected ContentProviderBase(string name)
{ {
DisplayName = Name = name; DisplayName = Name = name;
} }
public virtual Task OnEnter() => Task.CompletedTask; public virtual Task OnEnter() => Task.CompletedTask;
public virtual async Task<IItem> GetItemByFullNameAsync(FullName fullName) => await GetItemByNativePathAsync(GetNativePath(fullName)); public virtual async Task<IItem> GetItemByFullNameAsync(FullName fullName, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown)
public abstract Task<IItem> GetItemByNativePathAsync(NativePath nativePath); => await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType);
public abstract Task<IItem> GetItemByNativePathAsync(NativePath nativePath, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown);
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);
} }

View File

@@ -11,7 +11,7 @@ namespace FileTime.Core.Services
private readonly List<ItemsTransformator> _transformators = new(); private readonly List<ItemsTransformator> _transformators = new();
private IAbsolutePath? _currentSelectedItemCached; private IAbsolutePath? _currentSelectedItemCached;
public IObservable<IContainer?> CurrentLocation { get; } public IObservable<IContainer?> CurrentLocation { get; }
public IObservable<IEnumerable<IItem>> CurrentItems { get; } public IObservable<IEnumerable<IItem>?> CurrentItems { get; }
public IObservable<IAbsolutePath?> CurrentSelectedItem { get; } public IObservable<IAbsolutePath?> CurrentSelectedItem { get; }
public Tab() public Tab()
@@ -23,7 +23,7 @@ namespace FileTime.Core.Services
.Where(c => c is not null) .Where(c => c is not null)
.Select(c => c!.Items) .Select(c => c!.Items)
.Switch() .Switch()
.Select(i => Observable.FromAsync(async () => await MapItems(i))) .Select(i => i == null ? Observable.Return<IEnumerable<IItem>?>(null) : Observable.FromAsync(async () => await MapItems(i)))
.Switch(), .Switch(),
CurrentLocation CurrentLocation
.Where(c => c is null) .Where(c => c is null)
@@ -43,21 +43,11 @@ namespace FileTime.Core.Services
CurrentSelectedItem.Subscribe(s => _currentSelectedItemCached = s); CurrentSelectedItem.Subscribe(s => _currentSelectedItemCached = s);
} }
private async Task<IEnumerable<IItem>> MapItems(IReadOnlyList<IAbsolutePath> items) private async Task<IEnumerable<IItem>> MapItems(IEnumerable<IAbsolutePath> items)
{ {
IEnumerable<IItem> resolvedItems = await items IEnumerable<IItem> resolvedItems = await items
.ToAsyncEnumerable() .ToAsyncEnumerable()
.SelectAwait( .SelectAwait(async i => await i.ResolveAsync(true))
async i =>
{
try
{
//TODO: force create by AbsolutePath name
return await i.ContentProvider.GetItemByFullNameAsync(i.Path);
}
catch { return null!; }
}
)
.Where(i => i != null) .Where(i => i != null)
.ToListAsync(); .ToListAsync();
@@ -79,7 +69,7 @@ namespace FileTime.Core.Services
private IObservable<IAbsolutePath?> GetSelectedItemByLocation(IContainer? currentLocation) private IObservable<IAbsolutePath?> GetSelectedItemByLocation(IContainer? currentLocation)
{ {
//TODO: //TODO:
return currentLocation?.Items?.Select(i => i.Count == 0 ? null : i[0]) ?? Observable.Return((IAbsolutePath?)null); return currentLocation?.Items?.Select(i => i.FirstOrDefault()) ?? Observable.Return((IAbsolutePath?)null);
} }
public void SetCurrentLocation(IContainer newLocation) => _currentLocation.OnNext(newLocation); public void SetCurrentLocation(IContainer newLocation) => _currentLocation.OnNext(newLocation);

View File

@@ -1,8 +1,9 @@
<Application xmlns="https://github.com/avaloniaui" <Application
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:FileTime.GuiApp"
x:Class="FileTime.GuiApp.App" x:Class="FileTime.GuiApp.App"
xmlns:converters="using:FileTime.GuiApp.Converters"> xmlns="https://github.com/avaloniaui"
xmlns:converters="using:FileTime.GuiApp.Converters"
xmlns:local="using:FileTime.GuiApp"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources> <Application.Resources>
<ResourceDictionary> <ResourceDictionary>
<Color x:Key="AppBackgroundColor">#E7073642</Color> <Color x:Key="AppBackgroundColor">#E7073642</Color>
@@ -30,111 +31,74 @@
<Color x:Key="ErrorColor">#dc322f</Color> <Color x:Key="ErrorColor">#dc322f</Color>
<LinearGradientBrush x:Key="ContentSeparatorBrush"> <LinearGradientBrush x:Key="ContentSeparatorBrush">
<GradientStop Offset="0" Color="#0093a1a1" /> <GradientStop Color="#0093a1a1" Offset="0" />
<GradientStop Offset="0.5" Color="#93a1a1" /> <GradientStop Color="#93a1a1" Offset="0.5" />
<GradientStop Offset="1" Color="#0093a1a1" /> <GradientStop Color="#0093a1a1" Offset="1" />
</LinearGradientBrush> </LinearGradientBrush>
<LinearGradientBrush x:Key="ContainerGradientBackgroundBrush"> <LinearGradientBrush x:Key="ContainerGradientBackgroundBrush">
<GradientStop Offset="0" Color="#00b58900" /> <GradientStop Color="#00b58900" Offset="0" />
<GradientStop Offset="0.4" Color="#CCb58900" /> <GradientStop Color="#CCb58900" Offset="0.4" />
<GradientStop Offset="0.6" Color="#CCb58900" /> <GradientStop Color="#CCb58900" Offset="0.6" />
<GradientStop Offset="1" Color="#00b58900" /> <GradientStop Color="#00b58900" Offset="1" />
</LinearGradientBrush> </LinearGradientBrush>
<SolidColorBrush <SolidColorBrush Color="{DynamicResource AppBackgroundColor}" x:Key="AppBackgroundBrush" />
x:Key="AppBackgroundBrush" <SolidColorBrush Color="{DynamicResource ContainerBackgroundColor}" x:Key="ContainerBackgroundBrush" />
Color="{DynamicResource AppBackgroundColor}" /> <SolidColorBrush Color="{DynamicResource TransparentContainerBackgroundColor}" x:Key="TransparentContainerBackgroundBrush" />
<SolidColorBrush
x:Key="ContainerBackgroundBrush"
Color="{DynamicResource ContainerBackgroundColor}" />
<SolidColorBrush
x:Key="TransparentContainerBackgroundBrush"
Color="{DynamicResource TransparentContainerBackgroundColor}" />
<SolidColorBrush <SolidColorBrush Color="{DynamicResource ItemBackgroundColor}" x:Key="ItemBackgroundBrush" />
x:Key="ItemBackgroundBrush" <SolidColorBrush Color="{DynamicResource AlternativeItemBackgroundColor}" x:Key="AlternativeItemBackgroundBrush" />
Color="{DynamicResource ItemBackgroundColor}" /> <SolidColorBrush Color="{DynamicResource SelectedItemBackgroundColor}" x:Key="SelectedItemBackgroundBrush" />
<SolidColorBrush <SolidColorBrush Color="{DynamicResource MarkedItemBackgroundColor}" x:Key="MarkedItemBackgroundBrush" />
x:Key="AlternativeItemBackgroundBrush" <SolidColorBrush Color="{DynamicResource MarkedSelectedItemBackgroundColor}" x:Key="MarkedSelectedItemBackgroundBrush" />
Color="{DynamicResource AlternativeItemBackgroundColor}" /> <SolidColorBrush Color="{DynamicResource MarkedAlternativeItemBackgroundColor}" x:Key="MarkedAlternativeItemBackgroundBrush" />
<SolidColorBrush
x:Key="SelectedItemBackgroundBrush"
Color="{DynamicResource SelectedItemBackgroundColor}" />
<SolidColorBrush
x:Key="MarkedItemBackgroundBrush"
Color="{DynamicResource MarkedItemBackgroundColor}" />
<SolidColorBrush
x:Key="MarkedSelectedItemBackgroundBrush"
Color="{DynamicResource MarkedSelectedItemBackgroundColor}" />
<SolidColorBrush
x:Key="MarkedAlternativeItemBackgroundBrush"
Color="{DynamicResource MarkedAlternativeItemBackgroundColor}" />
<SolidColorBrush <SolidColorBrush Color="{DynamicResource ForegroundColor}" x:Key="ForegroundBrush" />
x:Key="ForegroundBrush" <SolidColorBrush Color="{DynamicResource AccentColor}" x:Key="AccentBrush" />
Color="{DynamicResource ForegroundColor}" /> <SolidColorBrush Color="{DynamicResource AccentComplementColor}" x:Key="AccentComplementBrush" />
<SolidColorBrush <SolidColorBrush Color="{DynamicResource LightForegroundColor}" x:Key="LightForegroundBrush" />
x:Key="AccentBrush" <SolidColorBrush Color="{DynamicResource AlternativeItemForegroundColor}" x:Key="AlternativeItemForegroundBrush" />
Color="{DynamicResource AccentColor}" /> <SolidColorBrush Color="{DynamicResource SelectedItemForegroundColor}" x:Key="SelectedItemForegroundBrush" />
<SolidColorBrush <SolidColorBrush Color="{DynamicResource MarkedItemForegroundColor}" x:Key="MarkedItemForegroundBrush" />
x:Key="AccentComplementBrush" <SolidColorBrush Color="{DynamicResource MarkedAlternativeItemForegroundColor}" x:Key="MarkedAlternativeItemForegroundBrush" />
Color="{DynamicResource AccentComplementColor}" /> <SolidColorBrush Color="{DynamicResource MarkedSelectedItemForegroundColor}" x:Key="MarkedSelectedItemForegroundBrush" />
<SolidColorBrush
x:Key="LightForegroundBrush"
Color="{DynamicResource LightForegroundColor}" />
<SolidColorBrush
x:Key="AlternativeItemForegroundBrush"
Color="{DynamicResource AlternativeItemForegroundColor}" />
<SolidColorBrush
x:Key="SelectedItemForegroundBrush"
Color="{DynamicResource SelectedItemForegroundColor}" />
<SolidColorBrush
x:Key="MarkedItemForegroundBrush"
Color="{DynamicResource MarkedItemForegroundColor}" />
<SolidColorBrush
x:Key="MarkedAlternativeItemForegroundBrush"
Color="{DynamicResource MarkedAlternativeItemForegroundColor}" />
<SolidColorBrush
x:Key="MarkedSelectedItemForegroundBrush"
Color="{DynamicResource MarkedSelectedItemForegroundColor}" />
<SolidColorBrush <SolidColorBrush Color="{DynamicResource ErrorColor}" x:Key="ErrorBrush" />
x:Key="ErrorBrush"
Color="{DynamicResource ErrorColor}" />
<SolidColorBrush x:Key="SystemControlHighlightListAccentLowBrush" Color="{DynamicResource SelectedItemBackgroundColor}" /> <SolidColorBrush Color="{DynamicResource SelectedItemBackgroundColor}" x:Key="SystemControlHighlightListAccentLowBrush" />
<converters:ItemViewModeToBrushConverter <converters:ItemViewModeToBrushConverter
x:Key="ItemViewModeToForegroundConverter"
DefaultBrush="{StaticResource ForegroundBrush}"
AlternativeBrush="{StaticResource AlternativeItemForegroundBrush}" AlternativeBrush="{StaticResource AlternativeItemForegroundBrush}"
SelectedBrush="{StaticResource SelectedItemForegroundBrush}" DefaultBrush="{StaticResource ForegroundBrush}"
MarkedBrush="{StaticResource MarkedItemForegroundBrush}"
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemForegroundBrush}" MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemForegroundBrush}"
MarkedSelectedBrush="{StaticResource MarkedSelectedItemForegroundBrush}"/> MarkedBrush="{StaticResource MarkedItemForegroundBrush}"
MarkedSelectedBrush="{StaticResource MarkedSelectedItemForegroundBrush}"
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"
x:Key="ItemViewModeToForegroundConverter" />
<converters:ItemViewModeToBrushConverter <converters:ItemViewModeToBrushConverter
x:Key="ItemViewModeToBackgroundConverter"
DefaultBrush="{StaticResource ItemBackgroundBrush}"
AlternativeBrush="{StaticResource AlternativeItemBackgroundBrush}" AlternativeBrush="{StaticResource AlternativeItemBackgroundBrush}"
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}" DefaultBrush="{StaticResource ItemBackgroundBrush}"
MarkedBrush="{StaticResource MarkedItemBackgroundBrush}"
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemBackgroundBrush}" MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemBackgroundBrush}"
MarkedSelectedBrush="{StaticResource MarkedSelectedItemBackgroundBrush}"/> MarkedBrush="{StaticResource MarkedItemBackgroundBrush}"
MarkedSelectedBrush="{StaticResource MarkedSelectedItemBackgroundBrush}"
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"
x:Key="ItemViewModeToBackgroundConverter" />
<converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter" /> <converters:NamePartShrinkerConverter x:Key="NamePartShrinkerConverter" />
<converters:ItemViewModelIsAttributeTypeConverter x:Key="ItemViewModelIsAttributeTypeConverter" /> <converters:ItemViewModelIsAttributeTypeConverter x:Key="ItemViewModelIsAttributeTypeConverter" />
<converters:ItemViewModelIsAttributeTypeConverter x:Key="ItemViewModelIsNotAttributeTypeConverter" Invert="true"/> <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="EqualityConverter" /> <converters:CompareConverter x:Key="EqualityConverter" />
<converters:CompareConverter x:Key="NotEqualsConverter" ComparisonCondition="{x:Static converters:ComparisonCondition.NotEqual}"/> <converters:CompareConverter ComparisonCondition="{x:Static converters:ComparisonCondition.NotEqual}" x:Key="NotEqualsConverter" />
<converters:ExceptionToStringConverter x:Key="ExceptionToStringConverter" />
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>
@@ -159,7 +123,7 @@
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="HorizontalAlignment" Value="Stretch" /> <Setter Property="HorizontalAlignment" Value="Stretch" />
</Style> </Style>
<Style Selector="ListBox.ContentListView > ListBoxItem"> <Style Selector="ListBox.ContentListView &gt; ListBoxItem">
<Setter Property="Margin" Value="0" /> <Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" /> <Setter Property="Padding" Value="0" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
@@ -193,9 +157,9 @@
<Border> <Border>
<RadioButton <RadioButton
Content="{TemplateBinding ContentPresenter.Content}" Content="{TemplateBinding ContentPresenter.Content}"
VerticalAlignment="Center" Foreground="{DynamicResource ForegroundBrush}"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
Foreground="{DynamicResource ForegroundBrush}"/> VerticalAlignment="Center" />
</Border> </Border>
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
@@ -204,7 +168,10 @@
<Style Selector="Image.LoadingAnimation"> <Style Selector="Image.LoadingAnimation">
<Style.Animations> <Style.Animations>
<Animation Duration="0:0:2" IterationCount="INFINITE" Easing="QuadraticEaseInOut"> <Animation
Duration="0:0:2"
Easing="QuadraticEaseInOut"
IterationCount="INFINITE">
<KeyFrame Cue="0%"> <KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="45" /> <Setter Property="RotateTransform.Angle" Value="45" />
</KeyFrame> </KeyFrame>

View File

@@ -0,0 +1,45 @@
using System.Globalization;
using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters
{
public class ExceptionToStringConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is not Exception e) return value;
if (e is UnauthorizedAccessException)
{
return e.Message;
}
else if (e.InnerException != null)
{
return TraverseInnerException(e);
}
return FormatException(e);
}
private static string TraverseInnerException(Exception e)
{
string s = "";
if (e.InnerException != null) s += TraverseInnerException(e.InnerException) + Environment.NewLine;
else return FormatException(e);
s += "In: " + FormatException(e);
return s;
}
private static string FormatException(Exception e)
{
return $"{e.Message} ({e.GetType().FullName})";
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,21 +1,27 @@
<Window xmlns="https://github.com/avaloniaui" <Window
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FileTime.GuiApp.ViewModels"
xmlns:corevm="using:FileTime.App.Core.ViewModels"
xmlns:local="using:FileTime.GuiApp.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
InputElement.KeyDown="OnKeyDown"
Opened="OnWindowOpened"
x:Class="FileTime.GuiApp.Views.MainWindow"
Icon="/Assets/filetime.ico"
Title="FileTime"
TransparencyLevelHint="Blur"
Background="Transparent" Background="Transparent"
ExtendClientAreaToDecorationsHint="True" ExtendClientAreaToDecorationsHint="True"
Icon="/Assets/filetime.ico"
InputElement.KeyDown="OnKeyDown"
Opened="OnWindowOpened"
Title="FileTime"
TransparencyLevelHint="Blur"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d"
x:Class="FileTime.GuiApp.Views.MainWindow"
x:CompileBindings="True"
x:DataType="vm:IMainWindowViewModelBase" x:DataType="vm:IMainWindowViewModelBase"
x:CompileBindings="True"> xmlns="https://github.com/avaloniaui"
xmlns:corevm="using:FileTime.App.Core.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:FileTime.GuiApp.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FileTime.GuiApp.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.DataContext>
<vm:MainWindowViewModel />
</Design.DataContext>
<Grid Background="{DynamicResource AppBackgroundBrush}"> <Grid Background="{DynamicResource AppBackgroundBrush}">
<Grid IsVisible="{Binding Loading, Converter={x:Static BoolConverters.Not}}" x:DataType="vm:MainWindowViewModel"> <Grid IsVisible="{Binding Loading, Converter={x:Static BoolConverters.Not}}" x:DataType="vm:MainWindowViewModel">
@@ -30,16 +36,16 @@
<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^.CurrentLocation^.FullName.Path,Converter={StaticResource PathPreformatter}}"/ -->
<TextBlock <TextBlock Foreground="{StaticResource AccentBrush}" Text="{Binding AppState.SelectedTab^.CurrentSelectedItem^.DisplayNameText}" />
Text="{Binding AppState.SelectedTab^.CurrentSelectedItem^.DisplayNameText}" Foreground="{StaticResource AccentBrush}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
<Grid Grid.Column="1" Grid.Row="1" RowDefinitions="Auto,40,*,Auto"> <Grid
Grid.Column="1"
<ItemsControl
Grid.Row="1" Grid.Row="1"
Items="{Binding AppState.Tabs}"> RowDefinitions="Auto,40,*,Auto">
<ItemsControl Grid.Row="1" Items="{Binding AppState.Tabs}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" /> <StackPanel Orientation="Horizontal" />
@@ -48,24 +54,26 @@
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid RowDefinitions="Auto,1"> <Grid RowDefinitions="Auto,1">
<StackPanel Orientation="Horizontal" Margin="20,0,20,0"> <StackPanel Margin="20,0,20,0" Orientation="Horizontal">
<TextBlock Text="{Binding TabNumber, StringFormat=({0})}" VerticalAlignment="Center" />
<TextBlock <TextBlock
VerticalAlignment="Center" Text="{Binding TabNumber,StringFormat=({0})}" /> Margin="5,0,0,0"
Text="{Binding CurrentLocation^.Name}"
<TextBlock VerticalAlignment="Center" />
VerticalAlignment="Center" Margin="5,0,0,0" Text="{Binding CurrentLocation^.Name}" />
</StackPanel> </StackPanel>
<Rectangle Fill="{DynamicResource ForegroundBrush}" Grid.Row="1" IsVisible="{Binding IsSelected^}"/> <Rectangle
Fill="{DynamicResource ForegroundBrush}"
Grid.Row="1"
IsVisible="{Binding IsSelected^}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </ItemsControl>
<Grid <Grid Grid.Row="2" Margin="20,0,0,0">
Grid.Row="2"
Margin="20,0,0,0">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="15*" /> <ColumnDefinition Width="15*" />
@@ -77,56 +85,105 @@
<Rectangle <Rectangle
Fill="{DynamicResource ContentSeparatorBrush}"
Grid.Column="1" Grid.Column="1"
Width="1"
Margin="0,10,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Margin="0,10,0,10"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{DynamicResource ContentSeparatorBrush}" /> Width="1" />
<Grid Grid.Column="2" RowDefinitions="Auto,*"> <Grid Grid.Column="2" RowDefinitions="Auto,*">
<Grid IsVisible="{Binding AppState.SelectedTab^.CurrentLocation^.IsLoading^}"> <Grid IsVisible="{Binding AppState.SelectedTab^.CurrentLocation^.IsLoading^}">
<Image Width="40" Height="40" Source="{SvgImage /Assets/loading.svg}" Classes="LoadingAnimation"/> <Image
Classes="LoadingAnimation"
Height="40"
Source="{SvgImage /Assets/loading.svg}"
Width="40" />
</Grid> </Grid>
<ListBox <ListBox
Grid.Row="1"
x:Name="CurrentItems"
x:CompileBindings="False"
AutoScrollToSelectedItem="True" AutoScrollToSelectedItem="True"
Classes="ContentListView"
Grid.Row="1"
IsTabStop="True" IsTabStop="True"
Items="{Binding AppState.SelectedTab^.CurrentItems^}" Items="{Binding AppState.SelectedTab^.CurrentItems^}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible"
Classes="ContentListView"> x:CompileBindings="False"
x:Name="CurrentItems">
<ListBox.ItemTemplate> <ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel"> <DataTemplate x:DataType="corevm:IItemViewModel">
<local:ItemView HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch"/> <local:ItemView HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" />
</DataTemplate> </DataTemplate>
</ListBox.ItemTemplate> </ListBox.ItemTemplate>
</ListBox> </ListBox>
<TextBlock <TextBlock
Grid.Row="1"
x:CompileBindings="False"
x:Name="CurrentEmpty"
Margin="10"
HorizontalAlignment="Center"
FontWeight="Bold" FontWeight="Bold"
Foreground="{DynamicResource ErrorBrush}" Foreground="{DynamicResource ErrorBrush}"
IsVisible="{Binding AppState.SelectedTab^.CurrentLocation^.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}"> Grid.Row="1"
HorizontalAlignment="Center"
IsVisible="{Binding AppState.SelectedTab^.CurrentLocation^.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}"
Margin="10"
x:CompileBindings="False"
x:Name="CurrentEmpty">
Empty Empty
</TextBlock> </TextBlock>
</Grid> </Grid>
<Rectangle <Rectangle
Fill="{DynamicResource ContentSeparatorBrush}"
Grid.Column="3" Grid.Column="3"
Width="1"
Margin="0,10,0,10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Margin="0,10,0,10"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{DynamicResource ContentSeparatorBrush}" /> Width="1" />
<Grid Grid.Column="4">
<Grid IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren^, Converter={x:Static ObjectConverters.IsNotNull}}">
<ListBox
AutoScrollToSelectedItem="True"
Classes="ContentListView"
IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren^.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}"
Items="{Binding AppState.SelectedTab^.SelectedsChildren^}"
x:CompileBindings="False"
x:Name="ChildItems">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel">
<local:ItemView />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBlock
FontWeight="Bold"
Foreground="{DynamicResource ErrorBrush}"
HorizontalAlignment="Center"
IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}"
Margin="10"
x:CompileBindings="False"
x:Name="ChildEmpty">
Empty
</TextBlock>
</Grid>
<Grid IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren^, Converter={x:Static ObjectConverters.IsNull}, ConverterParameter=0}" RowDefinitions="Auto, Auto">
<TextBlock
Foreground="{DynamicResource ErrorBrush}"
HorizontalAlignment="Center"
Margin="0,0,0,10"
Text="There were some errors while opening container."
TextWrapping="Wrap" />
<ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab^.CurrentSelectedItem^.BaseItem.Exceptions^}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock Margin="5,0,5,10" Text="{Binding Converter={StaticResource ExceptionToStringConverter}}" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Grid>
</Grid>
</Grid> </Grid>
</Grid> </Grid>
</Grid> </Grid>
@@ -137,8 +194,14 @@
<Grid IsVisible="{Binding Loading}"> <Grid IsVisible="{Binding Loading}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<Image Source="/Assets/filetime.ico" Width="128" Height="128"/> <Image
<TextBlock Text="Loading..." HorizontalAlignment="Center" Margin="50"/> Height="128"
Source="/Assets/filetime.ico"
Width="128" />
<TextBlock
HorizontalAlignment="Center"
Margin="50"
Text="Loading..." />
</StackPanel> </StackPanel>
</Grid> </Grid>
</Grid> </Grid>

View File

@@ -31,9 +31,11 @@ namespace FileTime.Providers.Local
Items.OnNext(rootDirectories.Select(DirectoryToAbsolutePath).ToList()); Items.OnNext(rootDirectories.Select(DirectoryToAbsolutePath).ToList());
} }
public override Task<IItem> GetItemByNativePathAsync(NativePath nativePath) public override Task<IItem> GetItemByNativePathAsync(NativePath nativePath, bool forceResolve = false, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown)
{ {
var path = nativePath.Path; var path = nativePath.Path;
try
{
if ((path?.Length ?? 0) == 0) if ((path?.Length ?? 0) == 0)
{ {
return Task.FromResult((IItem)this); return Task.FromResult((IItem)this);
@@ -47,7 +49,62 @@ namespace FileTime.Providers.Local
return Task.FromResult((IItem)FileToElement(new FileInfo(path))); return Task.FromResult((IItem)FileToElement(new FileInfo(path)));
} }
throw new FileNotFoundException("Directory or file not found", path); var type = forceResolvePathType switch
{
AbsolutePathType.Container => "Directory",
AbsolutePathType.Element => "File",
_ => "Directory or file"
};
if (forceResolvePathType == AbsolutePathType.Container) throw new DirectoryNotFoundException($"{type} not found: '{path}'");
throw new FileNotFoundException(type + " not found", path);
}
catch (Exception e)
{
if (!forceResolve) throw new Exception($"Could not resolve path '{nativePath.Path}' and {nameof(forceResolve)} is false.", e);
return forceResolvePathType switch
{
AbsolutePathType.Container => Task.FromResult((IItem)CreateEmptyContainer(nativePath, Observable.Return(new List<Exception>() { e }))),
AbsolutePathType.Element => Task.FromResult(CreateEmptyElement(nativePath)),
_ => throw new Exception($"Could not resolve path '{nativePath.Path}' and could not force create, because {nameof(forceResolvePathType)} is {nameof(AbsolutePathType.Unknown)}.", e)
};
}
}
private Container CreateEmptyContainer(NativePath nativePath, IObservable<IEnumerable<Exception>>? exceptions = null)
{
var nonNullExceptions = exceptions ?? Observable.Return(Enumerable.Empty<Exception>());
var name = nativePath.Path.Split(Path.DirectorySeparatorChar).LastOrDefault() ?? "???";
var fullName = GetFullName(nativePath);
var parentFullName = fullName.GetParent();
var parent = new AbsolutePath(
this,
parentFullName ?? new FullName(""),
AbsolutePathType.Container);
return new Container(
name,
name,
fullName,
nativePath,
parent,
false,
true,
DateTime.MinValue,
SupportsDelete.False,
false,
"???",
this,
nonNullExceptions,
Observable.Return<IEnumerable<IAbsolutePath>?>(null)
);
}
private IItem CreateEmptyElement(NativePath nativePath)
{
throw new NotImplementedException();
} }
public override Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName) => Task.FromResult(GetItemsByContainer(fullName)); public override Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName) => Task.FromResult(GetItemsByContainer(fullName));
@@ -88,6 +145,7 @@ namespace FileTime.Providers.Local
true, true,
GetDirectoryAttributes(directoryInfo), GetDirectoryAttributes(directoryInfo),
this, this,
Observable.Return(Enumerable.Empty<Exception>()),
Observable.Return(GetItemsByContainer(directoryInfo)) Observable.Return(GetItemsByContainer(directoryInfo))
); );
} }
@@ -110,7 +168,8 @@ namespace FileTime.Providers.Local
SupportsDelete.True, SupportsDelete.True,
true, true,
GetFileAttributes(fileInfo), GetFileAttributes(fileInfo),
this this,
Observable.Return(Enumerable.Empty<Exception>())
); );
} }

42
src/Settings.XamlStyler Normal file
View File

@@ -0,0 +1,42 @@
{
"AttributesTolerance": 1,
"KeepFirstAttributeOnSameLine": false,
"MaxAttributeCharactersPerLine": 0,
"MaxAttributesPerLine": 1,
"NewlineExemptionElements": "RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter",
"SeparateByGroups": false,
"AttributeIndentation": 0,
"AttributeIndentationStyle": 1,
"RemoveDesignTimeReferences": false,
"EnableAttributeReordering": true,
"AttributeOrderingRuleGroups": [
"x:Class",
"xmlns, xmlns:x",
"xmlns:*",
"x:Key, Key, x:Name, Name, x:Uid, Uid, Title",
"DataContext",
"Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom",
"Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight",
"Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex",
"*:*, *",
"PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint",
"mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText",
"Storyboard.*, From, To, Duration"
],
"FirstLineAttributes": "",
"OrderAttributesByName": true,
"PutEndingBracketOnNewLine": false,
"RemoveEndingTagOfEmptyElement": true,
"SpaceBeforeClosingSlash": true,
"RootElementLineBreakRule": 0,
"ReorderVSM": 2,
"ReorderGridChildren": false,
"ReorderCanvasChildren": false,
"ReorderSetters": 0,
"FormatMarkupExtension": true,
"NoNewLineMarkupExtensions": "x:Bind, Binding",
"ThicknessSeparator": 2,
"ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin",
"FormatOnSave": true,
"CommentPadding": 2,
}