New reactive core WIP

This commit is contained in:
2023-07-21 22:15:48 +02:00
parent 342fc0d047
commit b61c204e49
61 changed files with 1605 additions and 413 deletions

View File

@@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
using DeclarativeProperty;
using FileTime.App.Core.ViewModels;
using FileTime.Core.Models;
using FileTime.GuiApp.Configuration;
@@ -12,7 +13,7 @@ public interface IGuiAppState : IAppState
bool IsAllShortcutVisible { get; set; }
bool NoCommandFound { get; set; }
List<CommandBindingConfiguration> PossibleCommands { get; set; }
BindedCollection<RootDriveInfo, string> RootDriveInfos { get; set; }
ObservableCollection<RootDriveInfo> RootDriveInfos { get; set; }
IReadOnlyList<PlaceInfo> Places { get; set; }
ObservableCollection<string> PopupTexts { get; }
IObservable<GuiPanel> ActivePanel { get; }

View File

@@ -27,7 +27,7 @@ public partial class GuiAppState : AppStateBase, IGuiAppState, IDisposable
[Property] private List<CommandBindingConfiguration> _possibleCommands = new();
[Property] private BindedCollection<RootDriveInfo, string> _rootDriveInfos = new();
[Property] private ObservableCollection<RootDriveInfo> _rootDriveInfos = new();
[Property] private IReadOnlyList<PlaceInfo> _places = new List<PlaceInfo>();

View File

@@ -10,7 +10,7 @@ using FileTime.GuiApp.ViewModels;
namespace FileTime.GuiApp.DesignPreview.Services;
public class GuiAppStatePreview : IGuiAppState
/*public class GuiAppStatePreview : IGuiAppState
{
public GuiAppStatePreview()
{
@@ -19,7 +19,7 @@ public class GuiAppStatePreview : IGuiAppState
CurrentSelectedTab = tab;
var tabs = new ObservableCollection<ITabViewModel>(new [] {tab});
Tabs = new ReadOnlyObservableCollection<ITabViewModel>(tabs);
Tabs = new ObservableCollection<ITabViewModel>(tabs);
SearchText = new BehaviorSubject<string?>(null);
ViewMode = new BehaviorSubject<ViewMode>(FileTime.App.Core.Models.Enums.ViewMode.Default);
@@ -30,7 +30,7 @@ public class GuiAppStatePreview : IGuiAppState
PopupTexts = new ObservableCollection<string>();
}
public ReadOnlyObservableCollection<ITabViewModel> Tabs { get; }
public ObservableCollection<ITabViewModel> Tabs { get; }
public IObservable<ITabViewModel> SelectedTab { get; }
public IObservable<string?> SearchText { get; }
public IObservable<ViewMode> ViewMode { get; }
@@ -56,4 +56,4 @@ public class GuiAppStatePreview : IGuiAppState
public ObservableCollection<string> PopupTexts { get; }
public IObservable<GuiPanel> ActivePanel { get; }
public void SetActivePanel(GuiPanel newPanel) => throw new NotImplementedException();
}
}*/

View File

@@ -1,4 +1,5 @@
using DynamicData;
using System.Collections.ObjectModel;
using DynamicData;
using FileTime.Core.Enums;
using FileTime.Core.Models;
using FileTime.Core.Timeline;
@@ -11,7 +12,7 @@ public class ItemPreview
static ItemPreview()
{
var exceptions = new SourceList<Exception>();
var exceptions = new ObservableCollection<Exception>();
CurrentContainer = new Container(
"HomePreview",
"HomePreview",
@@ -32,9 +33,9 @@ public class ItemPreview
null!,
false,
PointInTime.Present,
exceptions.Connect(),
exceptions,
new ExtensionCollection().AsReadOnly(),
new SourceCache<AbsolutePath, string>(a => a.Path.Path).Connect()
new ObservableCollection<AbsolutePath>()
);
}
@@ -58,7 +59,7 @@ public class ItemPreview
"attr",
null!,
PointInTime.Present,
new SourceList<Exception>().Connect(),
new ObservableCollection<Exception>(),
new ExtensionCollection().AsReadOnly()
);
}

View File

@@ -6,7 +6,7 @@ using FileTime.Core.Services;
namespace FileTime.GuiApp.DesignPreview.Services;
public class TabPreview : ITab
/*public class TabPreview : ITab
{
public TabPreview()
{
@@ -46,4 +46,4 @@ public class TabPreview : ITab
public void Dispose()
{
}
}
}*/

View File

@@ -9,7 +9,7 @@ using FileTime.Core.Services;
namespace FileTime.GuiApp.DesignPreview.Services;
public class TabViewModelPreview : ITabViewModel
/*public class TabViewModelPreview : ITabViewModel
{
private static readonly ItemNameConverterService _itemNameConverterService = new();
@@ -115,4 +115,4 @@ public class TabViewModelPreview : ITabViewModel
public void Dispose()
{
}
}
}*/

View File

@@ -1,6 +1,4 @@
<Styles
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
@@ -21,6 +19,9 @@
<Style Selector="TextBlock.ExtraSmallText">
<Setter Property="FontSize" Value="11" />
</Style>
<Style Selector="TextBlock.ErrorText">
<Setter Property="Foreground" Value="{DynamicResource ErrorBrush}" />
</Style>
<Style Selector="TextBox">
<Setter Property="Foreground" Value="{DynamicResource ForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource ContainerBackgroundBrush}" />
@@ -77,10 +78,10 @@
<ControlTemplate>
<Border>
<RadioButton
VerticalAlignment="Center"
Content="{TemplateBinding ContentPresenter.Content}"
Foreground="{DynamicResource ForegroundBrush}"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}" />
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
VerticalAlignment="Center" />
</Border>
</ControlTemplate>
</Setter.Value>
@@ -90,9 +91,9 @@
<Style Selector="Image.LoadingAnimation">
<Style.Animations>
<Animation
Duration="0:0:2"
Easing="QuadraticEaseInOut"
IterationCount="INFINITE"
Duration="0:0:2">
IterationCount="INFINITE">
<KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="45" />
</KeyFrame>

View File

@@ -1,47 +1,37 @@
using System.Runtime.InteropServices;
using DynamicData;
using DynamicData.Binding;
using FileTime.App.Core.Services;
using FileTime.Core.Extensions;
using FileTime.Core.Models;
using FileTime.GuiApp.ViewModels;
using FileTime.Providers.Local;
using ObservableComputations;
namespace FileTime.GuiApp.Services;
public class RootDriveInfoService : IStartupHandler
public class RootDriveInfoService : IStartupHandler, IDisposable
{
private readonly ILocalContentProvider _localContentProvider;
private readonly List<DriveInfo> _rootDrives = new();
private readonly OcConsumer _rootDriveInfosConsumer = new();
public RootDriveInfoService(
IGuiAppState guiAppState,
ILocalContentProvider localContentProvider)
{
_localContentProvider = localContentProvider;
InitRootDrives();
var rootDriveInfos = localContentProvider.Items.Transform(
i =>
{
var rootDrive = _rootDrives.FirstOrDefault(d =>
{
var containerPath = localContentProvider.GetNativePath(i.Path).Path;
var drivePath = d.Name.TrimEnd(Path.DirectorySeparatorChar);
return containerPath == drivePath
|| (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" &&
d.Name == "/");
});
return (Path: i, Drive: rootDrive);
}
var rootDriveInfos = localContentProvider.Items.Selecting<AbsolutePath, (AbsolutePath Path, DriveInfo? Drive)>(
i => MatchRootDrive(i)
)
.Filter(t => t.Drive is not null)
.TransformAsync(async t => (Item: await t.Path.ResolveAsyncSafe(), Drive: t.Drive!))
.Filter(t => t.Item is IContainer)
.Transform(t => (Container: (IContainer) t.Item!, t.Drive))
.Transform(t => new RootDriveInfo(t.Drive, t.Container))
.Sort(SortExpressionComparer<RootDriveInfo>.Ascending(d => d.Name));
.Filtering(t => IsNotNull(t.Drive))
.Selecting(t => Resolve(t))
.Filtering(t => t.Item is IContainer)
.Selecting(t => new RootDriveInfo(t.Drive, (IContainer)t.Item!))
.Ordering(d => d.Name);
guiAppState.RootDriveInfos = rootDriveInfos.ToBindedCollection();
rootDriveInfos.For(_rootDriveInfosConsumer);
guiAppState.RootDriveInfos = rootDriveInfos;
void InitRootDrives()
{
@@ -60,5 +50,29 @@ public class RootDriveInfoService : IStartupHandler
}
}
private static bool IsNotNull(object? obj) => obj is not null;
private static (IItem? Item, DriveInfo Drive) Resolve((AbsolutePath Path, DriveInfo? Drive) tuple)
{
var t = Task.Run(async () => await tuple.Path.ResolveAsyncSafe());
t.Wait();
return (Item: t.Result, Drive: tuple.Drive!);
}
private (AbsolutePath Path, DriveInfo? Drive) MatchRootDrive(AbsolutePath sourceItem)
{
var rootDrive = _rootDrives.FirstOrDefault(d =>
{
var containerPath = _localContentProvider.GetNativePath(sourceItem.Path).Path;
var drivePath = d.Name.TrimEnd(Path.DirectorySeparatorChar);
return containerPath == drivePath
|| (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && containerPath == "/" &&
d.Name == "/");
});
return (Path: sourceItem, Drive: rootDrive);
}
public Task InitAsync() => Task.CompletedTask;
public void Dispose() => _rootDriveInfosConsumer.Dispose();
}

View File

@@ -13,4 +13,5 @@ public interface IMainWindowViewModel : IMainWindowViewModelBase
IDialogService DialogService { get; }
IFrequencyNavigationService FrequencyNavigationService { get; }
ICommandPaletteService CommandPaletteService { get; }
public IRefreshSmoothnessCalculator RefreshSmoothnessCalculator { get; }
}

View File

@@ -7,7 +7,7 @@ using FileTime.GuiApp.Services;
namespace FileTime.GuiApp.ViewModels;
public class MainWindowDesignViewModel : IMainWindowViewModel
public class MainWindowDesignViewModel //: IMainWindowViewModel
{
public bool Loading => false;
public IObservable<string?> MainFont { get; } = new BehaviorSubject<string?>(null);
@@ -20,6 +20,6 @@ public class MainWindowDesignViewModel : IMainWindowViewModel
public MainWindowDesignViewModel()
{
AppState = new GuiAppStatePreview();
//AppState = new GuiAppStatePreview();
}
}

View File

@@ -28,6 +28,7 @@ namespace FileTime.GuiApp.ViewModels;
[Inject(typeof(IFontService), "_fontService")]
[Inject(typeof(IFrequencyNavigationService), PropertyAccessModifier = AccessModifier.Public)]
[Inject(typeof(ICommandPaletteService), PropertyAccessModifier = AccessModifier.Public)]
[Inject(typeof(IRefreshSmoothnessCalculator), PropertyAccessModifier = AccessModifier.Public)]
public partial class MainWindowViewModel : IMainWindowViewModel
{
public bool Loading => false;

View File

@@ -49,9 +49,14 @@
<Grid ColumnDefinitions="250,*" RowDefinitions="Auto,*">
<Grid PointerPressed="HeaderPointerPressed">
<!--<Grid PointerPressed="HeaderPointerPressed">
<Rectangle Fill="#01000000" />
<TextBlock Margin="15,10" Text="{Binding Title}" />
</Grid>-->
<Grid PointerPressed="HeaderPointerPressed">
<Rectangle Fill="#01000000" />
<TextBlock Margin="15,10" Text="{Binding RefreshSmoothnessCalculator.RefreshDelay}" />
</Grid>
<Grid Grid.Column="1" PointerPressed="HeaderPointerPressed">
@@ -59,7 +64,7 @@
<StackPanel Margin="20,10" Orientation="Horizontal">
<local:PathPresenter DataContext="{Binding AppState.SelectedTab^.CurrentLocation^.FullName.Path, Converter={StaticResource PathPreformatter}}" />
<TextBlock Foreground="{StaticResource AccentBrush}" Text="{Binding AppState.SelectedTab^.CurrentSelectedItem^.DisplayNameText}" />
<TextBlock Foreground="{StaticResource AccentBrush}" Text="{Binding AppState.SelectedTab^.CurrentSelectedItem.Value.DisplayNameText}" />
</StackPanel>
</Grid>
@@ -74,7 +79,7 @@
<TextBlock Margin="0,0,0,10" Text="Drives" />
<ItemsRepeater Grid.Row="1" ItemsSource="{Binding AppState.RootDriveInfos.Collection}">
<ItemsRepeater Grid.Row="1" ItemsSource="{Binding AppState.RootDriveInfos}">
<ItemsRepeater.ItemTemplate>
<DataTemplate x:DataType="vm:RootDriveInfo">
<Grid
@@ -356,7 +361,7 @@
<TextBlock
Margin="5,0,0,0"
Text="{Binding CurrentLocation^.Name, FallbackValue=Loading...}"
Text="{Binding CurrentLocation.Value.Name, FallbackValue=Loading...}"
VerticalAlignment="Center" />
</StackPanel>
@@ -376,10 +381,9 @@
AutoScrollToSelectedItem="True"
Classes="ContentListView"
IsTabStop="True"
ItemsSource="{Binding AppState.SelectedTab^.ParentsChildrenCollection.Collection}"
ItemsSource="{Binding AppState.SelectedTab^.ParentsChildren.Value}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Visible"
x:CompileBindings="False">
ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel">
<local:ItemView
@@ -399,33 +403,22 @@
Width="1" />
<Grid Grid.Column="2" RowDefinitions="Auto,*">
<Grid IsVisible="{Binding AppState.SelectedTab^.CurrentLocation^.IsLoading^, FallbackValue=False}">
<Grid IsVisible="{Binding AppState.SelectedTab^.CurrentLocation.Value.IsLoading^, FallbackValue=False}">
<Image
Classes="LoadingAnimation"
Height="40"
Source="{SvgImage /Assets/loading.svg}"
Width="40">
<!-- https://github.com/AvaloniaUI/Avalonia/issues/8791 -->
<!-- Has to explicitly set RotateTransform or animation does not work -->
<Image.RenderTransform>
<TransformGroup>
<RotateTransform />
</TransformGroup>
</Image.RenderTransform>
</Image>
Width="40" />
</Grid>
<!-- Note: On Windows CurrentItemsCollection is not sorted (for some reason) -->
<!-- Although CurrentItemsCollectionObservable is slower, but it is sorted -->
<!-- This should be fixed (works on linux tho...) -->
<ListBox
AutoScrollToSelectedItem="True"
Classes="ContentListView"
Grid.Row="1"
IsTabStop="True"
ItemsSource="{Binding AppState.SelectedTab^.CurrentItemsCollectionObservable^}"
ItemsSource="{Binding AppState.SelectedTab^.CurrentItems.Value}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
ScrollViewer.VerticalScrollBarVisibility="Visible"
SelectedItem="{Binding AppState.SelectedTab^.CurrentSelectedItem^}"
SelectedItem="{Binding AppState.SelectedTab^.CurrentSelectedItem.Value}"
x:Name="CurrentItems">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel">
@@ -439,9 +432,8 @@
Foreground="{DynamicResource ErrorBrush}"
Grid.Row="1"
HorizontalAlignment="Center"
IsVisible="{Binding AppState.SelectedTab^.CurrentItemsCollection.Collection.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}"
IsVisible="{Binding AppState.SelectedTab^.CurrentItems.Value.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}"
Margin="10"
x:CompileBindings="False"
x:Name="CurrentEmpty">
Empty
</TextBlock>
@@ -457,34 +449,56 @@
<Grid Grid.Column="4">
<Grid IsVisible="{Binding ItemPreviewService.ItemPreview^, Converter={x:Static ObjectConverters.IsNull}}">
<Grid IsVisible="{Binding AppState.SelectedTab^.SelectedsChildrenCollection.Collection, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}">
<ListBox
AutoScrollToSelectedItem="True"
Classes="ContentListView"
IsVisible="{Binding AppState.SelectedTab^.SelectedsChildrenCollection.Collection.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}"
ItemsSource="{Binding AppState.SelectedTab^.SelectedsChildrenCollection.Collection}"
x:CompileBindings="False"
x:Name="ChildItems">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel">
<local:ItemView />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren.Value, Converter={x:Static ObjectConverters.IsNotNull}, FallbackValue=False}">
<Grid RowDefinitions="Auto, Auto, *">
<Grid IsVisible="{Binding AppState.SelectedTab^.CurrentSelectedItemAsContainer.Value.Container.IsLoading^, FallbackValue=False}">
<Image
Classes="LoadingAnimation"
Height="40"
Source="{SvgImage /Assets/loading.svg}"
Width="40" />
</Grid>
<ItemsRepeater Grid.Row="1" ItemsSource="{Binding AppState.SelectedTab^.CurrentLocation.Value.Exceptions}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock
Classes="ErrorText"
Margin="5,0,5,10"
Text="{Binding Converter={StaticResource ExceptionToStringConverter}}" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
<ListBox
AutoScrollToSelectedItem="True"
Classes="ContentListView"
Grid.Row="2"
IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren.Value.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}"
ItemsSource="{Binding AppState.SelectedTab^.SelectedsChildren.Value}"
x:Name="ChildItems">
<ListBox.ItemTemplate>
<DataTemplate x:DataType="corevm:IItemViewModel">
<local:ItemView />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
<TextBlock
FontWeight="Bold"
Foreground="{DynamicResource ErrorBrush}"
HorizontalAlignment="Center"
IsVisible="{Binding AppState.SelectedTab^.SelectedsChildrenCollection.Collection.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}"
IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren.Value.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}"
Margin="10"
x:CompileBindings="False"
x:Name="ChildEmpty">
Empty
</TextBlock>
</Grid>
<Grid IsVisible="{Binding AppState.SelectedTab^.SelectedsChildrenCollection, Converter={x:Static ObjectConverters.IsNull}, ConverterParameter=0, FallbackValue=False}" RowDefinitions="Auto, Auto">
<Grid IsVisible="{Binding AppState.SelectedTab^.SelectedsChildren.Value, Converter={x:Static ObjectConverters.IsNull}, ConverterParameter=0, FallbackValue=False}" RowDefinitions="Auto, Auto">
<TextBlock
Foreground="{DynamicResource ErrorBrush}"
HorizontalAlignment="Center"
@@ -492,10 +506,13 @@
Text="There were some errors while opening container."
TextWrapping="Wrap" />
<ItemsRepeater Grid.Row="1" ItemsSource="{Binding AppState.SelectedTab^.CurrentSelectedItem^.BaseItem.Exceptions^}">
<ItemsRepeater Grid.Row="1" ItemsSource="{Binding AppState.SelectedTab^.CurrentSelectedItem.Value.BaseItem.Exceptions}">
<ItemsRepeater.ItemTemplate>
<DataTemplate>
<TextBlock Margin="5,0,5,10" Text="{Binding Converter={StaticResource ExceptionToStringConverter}}" />
<TextBlock
Classes="ErrorText"
Margin="5,0,5,10"
Text="{Binding Converter={StaticResource ExceptionToStringConverter}}" />
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>