Enter/Exit folder

This commit is contained in:
2022-04-11 22:09:32 +02:00
parent b6b8a7b3f8
commit 6245744612
56 changed files with 835 additions and 152 deletions

View File

@@ -0,0 +1,8 @@
namespace FileTime.App.Core.Models.Enums
{
public enum ViewMode
{
Default,
RapidTravel
}
}

View File

@@ -0,0 +1,9 @@
using FileTime.App.Core.Command;
namespace FileTime.App.Core.Services
{
public interface ICommandHandlerService
{
Task HandleCommandAsync(Commands command);
}
}

View File

@@ -1,14 +1,14 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.Models.Enums;
namespace FileTime.App.Core namespace FileTime.App.Core.ViewModels
{ {
public interface IAppState public interface IAppState
{ {
ObservableCollection<ITabViewModel> Tabs { get; } ObservableCollection<ITabViewModel> Tabs { get; }
ITabViewModel? SelectedTab { get; } IObservable<ITabViewModel?> SelectedTab { get; }
IObservable<ITabViewModel?> SelectedTabObservable { get; }
IObservable<string?> SearchText { get; } IObservable<string?> SearchText { get; }
ViewMode ViewMode { get; }
void AddTab(ITabViewModel tabViewModel); void AddTab(ITabViewModel tabViewModel);
void RemoveTab(ITabViewModel tabViewModel); void RemoveTab(ITabViewModel tabViewModel);

View File

@@ -5,5 +5,6 @@ namespace FileTime.App.Core.ViewModels
{ {
public interface IContainerViewModel : IItemViewModel, IInitable<IContainer, ITabViewModel, int> public interface IContainerViewModel : IItemViewModel, IInitable<IContainer, ITabViewModel, int>
{ {
IContainer? Container { get; }
} }
} }

View File

@@ -12,7 +12,7 @@ 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<IReadOnlyList<IItemViewModel>>? CurrentItems { get; } IObservable<IEnumerable<IItemViewModel>>? CurrentItems { get; }
IObservable<IReadOnlyList<FullName>> MarkedItems { get; } IObservable<IEnumerable<FullName>> MarkedItems { get; }
} }
} }

View File

@@ -1,70 +0,0 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using FileTime.App.Core.ViewModels;
using MvvmGen;
namespace FileTime.App.Core
{
[ViewModel]
public abstract partial class AppStateBase : IAppState
{
private readonly BehaviorSubject<string?> _searchText = new(null);
private readonly BehaviorSubject<ITabViewModel?> _selectedTabObservable = new(null);
private ITabViewModel? _selectedTab;
public ObservableCollection<ITabViewModel> Tabs { get; } = new();
public IObservable<string?> SearchText { get; private set; }
public IObservable<ITabViewModel?> SelectedTabObservable { get; private set; }
public ITabViewModel? SelectedTab
{
get => _selectedTab;
private set
{
if (value != _selectedTab)
{
_selectedTab = value;
OnPropertyChanged(nameof(SelectedTab));
_selectedTabObservable.OnNext(value);
}
}
}
partial void OnInitialize()
{
SearchText = _searchText.AsObservable();
SelectedTabObservable = _selectedTabObservable.AsObservable();
}
public void AddTab(ITabViewModel tabViewModel)
{
Tabs.Add(tabViewModel);
if (_selectedTab == null)
{
SelectedTab = Tabs.First();
}
}
public void RemoveTab(ITabViewModel tabViewModel)
{
if (!Tabs.Contains(tabViewModel)) return;
Tabs.Remove(tabViewModel);
if (_selectedTab == tabViewModel)
{
SelectedTab = Tabs.FirstOrDefault();
}
}
public void SetSearchText(string? searchText)
{
_searchText.OnNext(searchText);
}
public void SetSelectedTab(ITabViewModel tabToSelect)
{
SelectedTab = tabToSelect;
}
}
}

View File

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

View File

@@ -0,0 +1,104 @@
using System.Reactive.Linq;
using FileTime.App.Core.Command;
using FileTime.App.Core.ViewModels;
using FileTime.Core.Models;
namespace FileTime.App.Core.Services
{
public class CommandHandlerService : ICommandHandlerService
{
private readonly Dictionary<Commands, Func<Task>> _commandHandlers;
private readonly IAppState _appState;
private ITabViewModel? _selectedTab;
private IContainer? _currentLocation;
private IItemViewModel? _currentSelectedItem;
public CommandHandlerService(IAppState appState)
{
_appState = appState;
_appState.SelectedTab.Subscribe(t => _selectedTab = t);
_appState.SelectedTab.Select(t => t == null ? Observable.Return<IContainer?>(null) : t.CurrentLocation!).Switch().Subscribe(l => _currentLocation = l);
_appState.SelectedTab.Select(t => t == null ? Observable.Return<IItemViewModel?>(null) : t.CurrentSelectedItem!).Switch().Subscribe(l => _currentSelectedItem = l);
_commandHandlers = new Dictionary<Commands, Func<Task>>
{
//{Commands.AutoRefresh, ToggleAutoRefresh},
//{Commands.ChangeTimelineMode, ChangeTimelineMode},
//{Commands.CloseTab, CloseTab},
//{Commands.Compress, Compress},
//{Commands.Copy, Copy},
//{Commands.CopyHash, CopyHash},
//{Commands.CopyPath, CopyPath},
//{Commands.CreateContainer, CreateContainer},
//{Commands.CreateElement, CreateElement},
//{Commands.Cut, Cut},
//{Commands.Edit, Edit},
//{Commands.EnterRapidTravel, EnterRapidTravelMode},
//{Commands.FindByName, FindByName},
//{Commands.FindByNameRegex, FindByNameRegex},
//{Commands.GoToHome, GotToHome},
//{Commands.GoToPath, GoToContainer},
//{Commands.GoToProvider, GotToProvider},
//{Commands.GoToRoot, GotToRoot},
{Commands.GoUp, GoUp},
//{Commands.HardDelete, HardDelete},
//{Commands.Mark, MarkCurrentItem},
//{Commands.MoveCursorDown, MoveCursorDown},
//{Commands.MoveCursorDownPage, MoveCursorDownPage},
//{Commands.MoveCursorUp, MoveCursorUp},
//{Commands.MoveCursorUpPage, MoveCursorUpPage},
//{Commands.MoveToFirst, MoveToFirst},
//{Commands.MoveToLast, MoveToLast},
//{Commands.NextTimelineBlock, SelectNextTimelineBlock},
//{Commands.NextTimelineCommand, SelectNextTimelineCommand},
{Commands.Open, OpenContainer},
//{Commands.OpenInFileBrowser, OpenInDefaultFileExplorer},
//{Commands.OpenOrRun, OpenOrRun},
//{Commands.PasteMerge, PasteMerge},
//{Commands.PasteOverwrite, PasteOverwrite},
//{Commands.PasteSkip, PasteSkip},
//{Commands.PinFavorite, PinFavorite},
//{Commands.PreviousTimelineBlock, SelectPreviousTimelineBlock},
//{Commands.PreviousTimelineCommand, SelectPreviousTimelineCommand},
//{Commands.Refresh, RefreshCurrentLocation},
//{Commands.Rename, Rename},
//{Commands.RunCommand, RunCommandInContainer},
//{Commands.ScanContainerSize, ScanContainerSize},
//{Commands.ShowAllShotcut, ShowAllShortcut},
//{Commands.SoftDelete, SoftDelete},
//{Commands.SwitchToLastTab, async() => await SwitchToTab(-1)},
//{Commands.SwitchToTab1, async() => await SwitchToTab(1)},
//{Commands.SwitchToTab2, async() => await SwitchToTab(2)},
//{Commands.SwitchToTab3, async() => await SwitchToTab(3)},
//{Commands.SwitchToTab4, async() => await SwitchToTab(4)},
//{Commands.SwitchToTab5, async() => await SwitchToTab(5)},
//{Commands.SwitchToTab6, async() => await SwitchToTab(6)},
//{Commands.SwitchToTab7, async() => await SwitchToTab(7)},
//{Commands.SwitchToTab8, async() => await SwitchToTab(8)},
//{Commands.TimelinePause, PauseTimeline},
//{Commands.TimelineRefresh, RefreshTimeline},
//{Commands.TimelineStart, ContinueTimeline},
//{Commands.ToggleAdvancedIcons, ToggleAdvancedIcons},
//{Commands.ToggleHidden, ToggleHidden},
};
}
public async Task HandleCommandAsync(Commands command) =>
await _commandHandlers[command].Invoke();
private Task OpenContainer()
{
if (_currentSelectedItem is not IContainerViewModel containerViewModel || containerViewModel.Container is null) return Task.CompletedTask;
_selectedTab?.Tab?.SetCurrentLocation(containerViewModel.Container);
return Task.CompletedTask;
}
private async Task GoUp()
{
if (_currentLocation?.Parent is not IAbsolutePath parentPath || await parentPath.ResolveAsync() is not IContainer newContainer) return;
_selectedTab?.Tab?.SetCurrentLocation(newContainer);
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Collections.ObjectModel;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.ViewModels;
using MvvmGen;
using MoreLinq;
namespace FileTime.App.Core.ViewModels
{
[ViewModel]
public abstract partial class AppStateBase : IAppState
{
private readonly BehaviorSubject<string?> _searchText = new(null);
private readonly BehaviorSubject<ITabViewModel?> _selectedTab = new(null);
private readonly BehaviorSubject<IEnumerable<ITabViewModel>> _tabs = new(Enumerable.Empty<ITabViewModel>());
[Property]
private ViewMode _viewMode;
public ObservableCollection<ITabViewModel> Tabs { get; } = new();
public IObservable<string?> SearchText { get; private set; }
public IObservable<ITabViewModel?> SelectedTab { get; private set; }
partial void OnInitialize()
{
Tabs.CollectionChanged += (_, _) => _tabs.OnNext(Tabs);
SearchText = _searchText.AsObservable();
SelectedTab = Observable.CombineLatest(_tabs, _selectedTab, GetSelectedTab);
}
public void AddTab(ITabViewModel tabViewModel)
{
Tabs.Add(tabViewModel);
}
public void RemoveTab(ITabViewModel tabViewModel)
{
if (!Tabs.Contains(tabViewModel)) return;
Tabs.Remove(tabViewModel);
}
public void SetSearchText(string? searchText) => _searchText.OnNext(searchText);
public void SetSelectedTab(ITabViewModel tabToSelect) => _selectedTab.OnNext(tabToSelect);
private ITabViewModel? GetSelectedTab(IEnumerable<ITabViewModel> tabs, ITabViewModel? expectedSelectedTab)
{
var (prefered, others) = tabs.OrderBy(t => t.TabNumber).Partition(t => t.TabNumber >= (expectedSelectedTab?.TabNumber ?? 0));
return prefered.Concat(others).FirstOrDefault();
}
}
}

View File

@@ -7,6 +7,8 @@ namespace FileTime.App.Core.ViewModels
[ViewModel(GenerateConstructor = false)] [ViewModel(GenerateConstructor = false)]
public partial class ContainerViewModel : ItemViewModel, IContainerViewModel public partial class ContainerViewModel : ItemViewModel, IContainerViewModel
{ {
public IContainer? Container => BaseItem as IContainer;
public ContainerViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState) public ContainerViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState)
{ {
} }

View File

@@ -24,8 +24,8 @@ namespace FileTime.App.Core.ViewModels
public IObservable<IContainer?>? CurrentLocation { get; private set; } public IObservable<IContainer?>? CurrentLocation { get; private set; }
public IObservable<IItemViewModel?>? CurrentSelectedItem { get; private set; } public IObservable<IItemViewModel?>? CurrentSelectedItem { get; private set; }
public IObservable<IReadOnlyList<IItemViewModel>>? CurrentItems { get; private set; } public IObservable<IEnumerable<IItemViewModel>>? CurrentItems { get; private set; }
public IObservable<IReadOnlyList<FullName>> MarkedItems { get; } public IObservable<IEnumerable<FullName>> MarkedItems { get; }
public TabViewModel( public TabViewModel(
IServiceProvider serviceProvider, IServiceProvider serviceProvider,
@@ -37,7 +37,7 @@ namespace FileTime.App.Core.ViewModels
_appState = appState; _appState = appState;
MarkedItems = _markedItems.Select(e => e.ToList()).AsObservable(); MarkedItems = _markedItems.Select(e => e.ToList()).AsObservable();
IsSelected = _appState.SelectedTabObservable.Select(s => s == this); IsSelected = _appState.SelectedTab.Select(s => s == this);
} }
public void Init(ITab tab, int tabNumber) public void Init(ITab tab, int tabNumber)
@@ -46,11 +46,15 @@ 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()); CurrentItems = tab.CurrentItems.Select(items => items.Select(MapItemToViewModel).ToList()).Publish(Enumerable.Empty<IItemViewModel>()).RefCount();
CurrentSelectedItem = Observable.CombineLatest( CurrentSelectedItem =
CurrentItems, Observable.CombineLatest(
tab.CurrentSelectedItem, CurrentItems,
(currentItems, currentSelectedItemPath) => currentItems.FirstOrDefault(i => i.BaseItem?.FullName == currentSelectedItemPath?.Path)); tab.CurrentSelectedItem,
(currentItems, currentSelectedItemPath) => currentItems.FirstOrDefault(i => i.BaseItem?.FullName == currentSelectedItemPath?.Path)
)
.Publish(null)
.RefCount();
tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty<FullName>())); tab.CurrentLocation.Subscribe((_) => _markedItems.OnNext(Enumerable.Empty<FullName>()));
} }

View File

@@ -1,2 +1,7 @@
// See https://aka.ms/new-console-template for more information // See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!"); Console.WriteLine("Hello, World!");
var d1 = new DirectoryInfo("C:");
var d2 = new DirectoryInfo("C:\\");
;

View File

@@ -3,6 +3,5 @@ namespace FileTime.Core.Models
public static class Constants public static class Constants
{ {
public const char SeparatorChar = '/'; public const char SeparatorChar = '/';
public const int MaximumObservableMergeOperations = 4;
} }
} }

View File

@@ -6,7 +6,7 @@ namespace FileTime.Core.Models
{ {
if (Path is null) return null; if (Path is null) return null;
var pathParts = Path.Split(Constants.SeparatorChar); var pathParts = Path.TrimEnd(Constants.SeparatorChar).Split(Constants.SeparatorChar);
return pathParts.Length switch return pathParts.Length switch
{ {
> 1 => new(string.Join(Constants.SeparatorChar, pathParts.SkipLast(1))), > 1 => new(string.Join(Constants.SeparatorChar, pathParts.SkipLast(1))),

View File

@@ -9,5 +9,7 @@ namespace FileTime.Core.Models
IContentProvider? VirtualContentProvider { get; } IContentProvider? VirtualContentProvider { get; }
FullName Path { get; } FullName Path { get; }
AbsolutePathType Type { get; } AbsolutePathType Type { get; }
Task<IItem> ResolveAsync();
} }
} }

View File

@@ -9,7 +9,7 @@ namespace FileTime.Core.Models
string DisplayName { get; } string DisplayName { get; }
FullName? FullName { get; } FullName? FullName { get; }
NativePath? NativePath { get; } NativePath? NativePath { get; }
FullName? Parent { get; } IAbsolutePath? Parent { get; }
bool IsHidden { get; } bool IsHidden { get; }
bool IsExists { get; } bool IsExists { get; }
DateTime? CreatedAt { get; } DateTime? CreatedAt { get; }

View File

@@ -0,0 +1,7 @@
namespace FileTime.Core.Models
{
public record ItemsTransformator(
string Name,
Func<IEnumerable<IItem>, Task<IEnumerable<IItem>>> Transformator
);
}

View File

@@ -1,5 +1,6 @@
using FileTime.Core.Models; using FileTime.Core.Models;
using InitableService; using InitableService;
using System.Reactive.Subjects;
namespace FileTime.Core.Services namespace FileTime.Core.Services
{ {
@@ -9,6 +10,10 @@ namespace FileTime.Core.Services
IObservable<IAbsolutePath?> CurrentSelectedItem { get; } IObservable<IAbsolutePath?> CurrentSelectedItem { get; }
IObservable<IEnumerable<IItem>> CurrentItems { get; } IObservable<IEnumerable<IItem>> CurrentItems { get; }
void ChangeLocation(IContainer newLocation); void SetCurrentLocation(IContainer newLocation);
void AddSelectedItemsTransformator(ItemsTransformator transformator);
void RemoveSelectedItemsTransformator(ItemsTransformator transformator);
void RemoveSelectedItemsTransformatorByName(string name);
void SetSelectedItem(IAbsolutePath newSelectedItem);
} }
} }

View File

@@ -18,5 +18,11 @@ namespace FileTime.Core.Models
VirtualContentProvider = virtualContentProvider; VirtualContentProvider = virtualContentProvider;
Type = type; Type = type;
} }
public async Task<IItem> ResolveAsync()
{
var provider = VirtualContentProvider ?? ContentProvider;
return await provider.GetItemByFullNameAsync(Path);
}
} }
} }

View File

@@ -10,7 +10,7 @@ namespace FileTime.Core.Models
string DisplayName, string DisplayName,
FullName FullName, FullName FullName,
NativePath NativePath, NativePath NativePath,
FullName Parent, IAbsolutePath? Parent,
bool IsHidden, bool IsHidden,
bool IsExists, bool IsExists,
DateTime? CreatedAt, DateTime? CreatedAt,

View File

@@ -8,7 +8,7 @@ namespace FileTime.Core.Models
string DisplayName, string DisplayName,
FullName FullName, FullName FullName,
NativePath NativePath, NativePath NativePath,
FullName Parent, IAbsolutePath? Parent,
bool IsHidden, bool IsHidden,
bool IsExists, bool IsExists,
DateTime? CreatedAt, DateTime? CreatedAt,

View File

@@ -8,7 +8,7 @@ namespace FileTime.Core.Models
string DisplayName, string DisplayName,
FullName FullName, FullName FullName,
NativePath NativePath, NativePath NativePath,
FullName Parent, IAbsolutePath? Parent,
bool IsHidden, bool IsHidden,
bool IsExists, bool IsExists,
DateTime? CreatedAt, DateTime? CreatedAt,

View File

@@ -29,7 +29,7 @@ namespace FileTime.Core.Services
public IContentProvider Provider => this; public IContentProvider Provider => this;
public FullName? Parent => null; public IAbsolutePath? Parent => null;
public DateTime? CreatedAt => null; public DateTime? CreatedAt => null;

View File

@@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Interactive.Async" Version="6.0.1" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" /> <PackageReference Include="System.Linq.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

@@ -8,45 +8,71 @@ namespace FileTime.Core.Services
{ {
private readonly BehaviorSubject<IContainer?> _currentLocation = new(null); private readonly BehaviorSubject<IContainer?> _currentLocation = new(null);
private readonly BehaviorSubject<IAbsolutePath?> _currentSelectedItem = new(null); private readonly BehaviorSubject<IAbsolutePath?> _currentSelectedItem = new(null);
private readonly List<ItemsTransformator> _transformators = new();
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()
{ {
CurrentLocation = _currentLocation.AsObservable(); CurrentLocation = _currentLocation.DistinctUntilChanged().Do(_ => {; }).Publish(null).RefCount();
CurrentItems = CurrentItems =
Observable.Merge( Observable.Merge(
_currentLocation CurrentLocation
.Where(c => c is not null) .Where(c => c is not null)
.Select(c => c!.Items) .Select(c => c!.Items)
.Switch() .Switch()
.Select( .Select(i => Observable.FromAsync(async () => await MapItems(i)))
i => Observable.FromAsync(async () => .Switch(),
await i CurrentLocation
.ToAsyncEnumerable()
.SelectAwait(
async i =>
{
try
{
//TODO: force create by AbsolutePath name
return await i.ContentProvider.GetItemByFullNameAsync(i.Path);
}
catch { return null!; }
}
)
.Where(i => i != null)
.ToListAsync()
)
)
.Merge(Constants.MaximumObservableMergeOperations),
_currentLocation
.Where(c => c is null) .Where(c => c is null)
.Select(c => Enumerable.Empty<IItem>()) .Select(_ => Enumerable.Empty<IItem>())
); )
.Publish(Enumerable.Empty<IItem>())
.RefCount();
CurrentSelectedItem = CurrentLocation.Select(GetSelectedItemByLocation).Switch().Merge(_currentSelectedItem).Throttle(TimeSpan.FromMilliseconds(500)); CurrentSelectedItem = CurrentLocation
.Select(GetSelectedItemByLocation)
.Switch()
.Merge(_currentSelectedItem)
.DistinctUntilChanged()
.Publish(null)
.RefCount();
CurrentSelectedItem.Subscribe(s =>
{
_currentSelectedItemCached = s;
_currentSelectedItem.OnNext(s);
});
}
private async Task<IEnumerable<IItem>> MapItems(IReadOnlyList<IAbsolutePath> items)
{
IEnumerable<IItem> resolvedItems = await items
.ToAsyncEnumerable()
.SelectAwait(
async i =>
{
try
{
//TODO: force create by AbsolutePath name
return await i.ContentProvider.GetItemByFullNameAsync(i.Path);
}
catch { return null!; }
}
)
.Where(i => i != null)
.ToListAsync();
return _transformators.Count == 0
? resolvedItems
: (await _transformators
.ToAsyncEnumerable()
.Scan(resolvedItems, (acc, t) => new ValueTask<IEnumerable<IItem>>(t.Transformator(acc)))
.ToListAsync()
)
.SelectMany(t => t);
} }
public void Init(IContainer currentLocation) public void Init(IContainer currentLocation)
@@ -56,12 +82,25 @@ namespace FileTime.Core.Services
private IObservable<IAbsolutePath?> GetSelectedItemByLocation(IContainer? currentLocation) private IObservable<IAbsolutePath?> GetSelectedItemByLocation(IContainer? currentLocation)
{ {
//TODO:
return currentLocation?.Items?.Select(i => i.FirstOrDefault()) ?? Observable.Never((IAbsolutePath?)null); return currentLocation?.Items?.Select(i => i.FirstOrDefault()) ?? Observable.Never((IAbsolutePath?)null);
} }
public void ChangeLocation(IContainer newLocation) public void SetCurrentLocation(IContainer newLocation) => _currentLocation.OnNext(newLocation);
public void SetSelectedItem(IAbsolutePath newSelectedItem) => _currentSelectedItem.OnNext(newSelectedItem);
public void AddSelectedItemsTransformator(ItemsTransformator transformator) => _transformators.Add(transformator);
public void RemoveSelectedItemsTransformator(ItemsTransformator transformator) => _transformators.Remove(transformator);
public void RemoveSelectedItemsTransformatorByName(string name) => _transformators.RemoveAll(t => t.Name == name);
public async Task OpenSelected()
{ {
_currentLocation.OnNext(newLocation); if (_currentSelectedItemCached == null) return;
var resolvedSelectedItem = await _currentSelectedItemCached.ContentProvider.GetItemByFullNameAsync(_currentSelectedItemCached.Path);
if (resolvedSelectedItem is not IContainer resolvedContainer) return;
SetCurrentLocation(resolvedContainer);
} }
} }
} }

View File

@@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.GuiApp.CustomImpl"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Providers.Local.Abstractions", "Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj", "{1500A537-2116-4111-B216-7632040619B0}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.Providers.Local.Abstractions", "Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj", "{1500A537-2116-4111-B216-7632040619B0}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FileTime.GuiApp.Abstractions", "GuiApp\Avalonia\FileTime.GuiApp.Abstractions\FileTime.GuiApp.Abstractions.csproj", "{D7D1C76A-05B0-49BC-BCFF-06340E264EC1}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@@ -107,6 +109,10 @@ Global
{1500A537-2116-4111-B216-7632040619B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {1500A537-2116-4111-B216-7632040619B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1500A537-2116-4111-B216-7632040619B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {1500A537-2116-4111-B216-7632040619B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1500A537-2116-4111-B216-7632040619B0}.Release|Any CPU.Build.0 = Release|Any CPU {1500A537-2116-4111-B216-7632040619B0}.Release|Any CPU.Build.0 = Release|Any CPU
{D7D1C76A-05B0-49BC-BCFF-06340E264EC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D7D1C76A-05B0-49BC-BCFF-06340E264EC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D7D1C76A-05B0-49BC-BCFF-06340E264EC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D7D1C76A-05B0-49BC-BCFF-06340E264EC1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@@ -127,6 +133,7 @@ Global
{26331AB9-6E4D-40DB-8FF0-CB7133F67CA0} = {01F231DE-4A65-435F-B4BB-77EE5221890C} {26331AB9-6E4D-40DB-8FF0-CB7133F67CA0} = {01F231DE-4A65-435F-B4BB-77EE5221890C}
{4B742649-225F-4C73-B118-1B29FE2A5774} = {01F231DE-4A65-435F-B4BB-77EE5221890C} {4B742649-225F-4C73-B118-1B29FE2A5774} = {01F231DE-4A65-435F-B4BB-77EE5221890C}
{1500A537-2116-4111-B216-7632040619B0} = {2FC40FE1-4446-44AB-BF77-00F94D995FA3} {1500A537-2116-4111-B216-7632040619B0} = {2FC40FE1-4446-44AB-BF77-00F94D995FA3}
{D7D1C76A-05B0-49BC-BCFF-06340E264EC1} = {01F231DE-4A65-435F-B4BB-77EE5221890C}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF} SolutionGuid = {859FB3DF-C60A-46B1-82E5-90274905D1EF}

View File

@@ -1,6 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input; using Avalonia.Input;
using FileTime.App.Core.Command; using FileTime.App.Core.Command;

View File

@@ -1,5 +1,3 @@
using System.Collections.Generic;
namespace FileTime.GuiApp.Configuration namespace FileTime.GuiApp.Configuration
{ {
public class KeyBindingConfiguration public class KeyBindingConfiguration

View File

@@ -11,12 +11,22 @@ namespace FileTime.GuiApp.Configuration
public KeyConfig() { } public KeyConfig() { }
public KeyConfig(Key key, bool shift = false, bool alt = false, bool ctrl = false) public KeyConfig(
Key key,
bool shift = false,
bool alt = false,
bool ctrl = false)
{ {
Key = key; Key = key;
Shift = shift; Shift = shift;
Alt = alt; Alt = alt;
Ctrl = ctrl; Ctrl = ctrl;
} }
public bool AreEquals(KeyConfig otherKeyConfig) =>
Key == otherKeyConfig.Key
&& Alt == otherKeyConfig.Alt
&& Shift == otherKeyConfig.Shift
&& Ctrl == otherKeyConfig.Ctrl;
} }
} }

View File

@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,4 @@
namespace FileTime.GuiApp.Models
{
public record SpecialKeysStatus(bool IsAltPressed, bool IsShiftPressed, bool IsCtrlPressed);
}

View File

@@ -0,0 +1,4 @@
namespace FileTime.GuiApp.Services
{
public interface IDefaultModeKeyInputHandler : IKeyInputHandler { }
}

View File

@@ -0,0 +1,10 @@
using Avalonia.Input;
using FileTime.GuiApp.Models;
namespace FileTime.GuiApp.Services
{
public interface IKeyInputHandler
{
Task HandleInputKey(Key key, SpecialKeysStatus specialKeysStatus, Action<bool> setHandled);
}
}

View File

@@ -0,0 +1,9 @@
using Avalonia.Input;
namespace FileTime.GuiApp.Services
{
public interface IKeyInputHandlerService
{
Task ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled);
}
}

View File

@@ -0,0 +1,11 @@
using FileTime.GuiApp.Configuration;
namespace FileTime.GuiApp.Services
{
public interface IKeyboardConfigurationService
{
IReadOnlyList<CommandBindingConfiguration> CommandBindings { get; }
IReadOnlyList<CommandBindingConfiguration> UniversalCommandBindings { get; }
IReadOnlyList<CommandBindingConfiguration> AllShortcut { get; }
}
}

View File

@@ -0,0 +1,4 @@
namespace FileTime.GuiApp.Services
{
public interface IRapidTravelModeKeyInputHandler : IKeyInputHandler { }
}

View File

@@ -0,0 +1,14 @@
using FileTime.App.Core.ViewModels;
using FileTime.GuiApp.Configuration;
namespace FileTime.GuiApp.ViewModels
{
public interface IGuiAppState : IAppState
{
List<KeyConfig> PreviousKeys { get; }
bool IsAllShortcutVisible { get; set; }
bool NoCommandFound { get; set; }
string? MessageBoxText { get; set; }
List<CommandBindingConfiguration> PossibleCommands { get; set; }
}
}

View File

@@ -22,7 +22,7 @@ namespace FileTime.GuiApp
.InitSerilog(); .InitSerilog();
var logger = DI.ServiceProvider.GetRequiredService<ILogger<App>>(); var logger = DI.ServiceProvider.GetRequiredService<ILogger<App>>();
logger.LogInformation("App initialization completed."); logger.LogInformation("App initialization completed");
} }
public override void Initialize() public override void Initialize()
{ {

View File

@@ -6,6 +6,7 @@ using FileTime.App.Core.ViewModels;
using FileTime.Core.Services; using FileTime.Core.Services;
using FileTime.GuiApp.Configuration; using FileTime.GuiApp.Configuration;
using FileTime.GuiApp.Logging; using FileTime.GuiApp.Logging;
using FileTime.GuiApp.Services;
using FileTime.GuiApp.ViewModels; using FileTime.GuiApp.ViewModels;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@@ -20,14 +21,22 @@ namespace FileTime.GuiApp
{ {
return serviceCollection return serviceCollection
.AddSingleton<MainWindowViewModel>() .AddSingleton<MainWindowViewModel>()
.AddSingleton<AppState>() .AddSingleton<GuiAppState>()
.AddSingleton<IAppState, AppState>(s => s.GetRequiredService<AppState>()) .AddSingleton<IAppState, GuiAppState>(s => s.GetRequiredService<GuiAppState>())
.AddSingleton<IGuiAppState, GuiAppState>(s => s.GetRequiredService<GuiAppState>())
.AddSingleton<DefaultModeKeyInputHandler>()
.AddSingleton<RapidTravelModeKeyInputHandler>()
//TODO: move?? //TODO: move??
.AddTransient<ITab, Tab>() .AddTransient<ITab, Tab>()
.AddTransient<ITabViewModel, TabViewModel>() .AddTransient<ITabViewModel, TabViewModel>()
.AddTransient<IContainerViewModel, ContainerViewModel>() .AddTransient<IContainerViewModel, ContainerViewModel>()
.AddTransient<IElementViewModel, ElementViewModel>() .AddTransient<IElementViewModel, ElementViewModel>()
.AddTransient<IItemNameConverterService, ItemNameConverterService>(); .AddTransient<IItemNameConverterService, ItemNameConverterService>()
.AddSingleton<ICommandHandlerService, CommandHandlerService>()
.AddSingleton<IKeyInputHandlerService, KeyInputHandlerService>()
.AddSingleton<IDefaultModeKeyInputHandler, DefaultModeKeyInputHandler>()
.AddSingleton<IKeyboardConfigurationService, KeyboardConfigurationService>()
.AddSingleton<IRapidTravelModeKeyInputHandler, RapidTravelModeKeyInputHandler>();
} }
internal static IServiceCollection RegisterLogging(this IServiceCollection serviceCollection) internal static IServiceCollection RegisterLogging(this IServiceCollection serviceCollection)

View File

@@ -8,6 +8,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" /> <ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core\FileTime.App.Core.csproj" />
<ProjectReference Include="..\FileTime.GuiApp.Abstractions\FileTime.GuiApp.Abstractions.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,6 +0,0 @@
using FileTime.App.Core;
namespace FileTime.GuiApp.ViewModels
{
public class AppState : AppStateBase { }
}

View File

@@ -0,0 +1,24 @@
using FileTime.App.Core.ViewModels;
using FileTime.GuiApp.Configuration;
using MvvmGen;
namespace FileTime.GuiApp.ViewModels
{
[ViewModel]
public partial class GuiAppState : AppStateBase, IGuiAppState
{
[Property]
private bool _isAllShortcutVisible;
[Property]
private bool _noCommandFound;
[Property]
private string? _messageBoxText;
[Property]
private List<CommandBindingConfiguration> _possibleCommands = new();
public List<KeyConfig> PreviousKeys { get; } = new();
}
}

View File

@@ -0,0 +1,10 @@
using FileTime.GuiApp.Configuration;
namespace FileTime.GuiApp.Extensions
{
public static class KeyConfigExtensions
{
public static bool AreKeysEqual(this IReadOnlyList<KeyConfig> collection1, IReadOnlyList<KeyConfig> collection2)
=> collection1.Count == collection2.Count && collection1.Zip(collection2).All(t => t.First.AreEquals(t.Second));
}
}

View File

@@ -33,6 +33,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" /> <ProjectReference Include="..\..\..\AppCommon\FileTime.App.Core.Abstraction\FileTime.App.Core.Abstraction.csproj" />
<ProjectReference Include="..\..\..\Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj" /> <ProjectReference Include="..\..\..\Providers\FileTime.Providers.Local.Abstractions\FileTime.Providers.Local.Abstractions.csproj" />
<ProjectReference Include="..\FileTime.GuiApp.Abstractions\FileTime.GuiApp.Abstractions.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -0,0 +1,151 @@
using System.Reactive.Linq;
using Avalonia.Input;
using FileTime.App.Core.Command;
using FileTime.App.Core.Services;
using FileTime.App.Core.ViewModels;
using FileTime.Core.Models;
using FileTime.GuiApp.Configuration;
using FileTime.GuiApp.Extensions;
using FileTime.GuiApp.Models;
using FileTime.GuiApp.ViewModels;
using Microsoft.Extensions.Logging;
namespace FileTime.GuiApp.Services
{
public class DefaultModeKeyInputHandler : IDefaultModeKeyInputHandler
{
private readonly IGuiAppState _appState;
private readonly IKeyboardConfigurationService _keyboardConfigurationService;
private readonly List<KeyConfig[]> _keysToSkip = new();
private ITabViewModel? _selectedTab;
private IContainer? _currentLocation;
private readonly ILogger<DefaultModeKeyInputHandler> _logger;
private readonly ICommandHandlerService _commandHandlerService;
public DefaultModeKeyInputHandler(
IGuiAppState appState,
IKeyboardConfigurationService keyboardConfigurationService,
ILogger<DefaultModeKeyInputHandler> logger,
ICommandHandlerService commandHandlerService)
{
_appState = appState;
_keyboardConfigurationService = keyboardConfigurationService;
_logger = logger;
_commandHandlerService = commandHandlerService;
_appState.SelectedTab.Subscribe(t => _selectedTab = t);
_appState.SelectedTab.Select(t => t == null ? Observable.Return<IContainer?>(null) : t.CurrentLocation!).Switch().Subscribe(l => _currentLocation = l);
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Up) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Down) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Tab) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.PageDown) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.PageUp) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.F4, alt: true) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.LWin) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.RWin) });
}
public async Task HandleInputKey(Key key, SpecialKeysStatus specialKeysStatus, Action<bool> setHandled)
{
var keyWithModifiers = new KeyConfig(key, shift: specialKeysStatus.IsShiftPressed, alt: specialKeysStatus.IsAltPressed, ctrl: specialKeysStatus.IsCtrlPressed);
_appState.PreviousKeys.Add(keyWithModifiers);
var selectedCommandBinding = _keyboardConfigurationService.UniversalCommandBindings.FirstOrDefault(c => c.Keys.AreKeysEqual(_appState.PreviousKeys));
selectedCommandBinding ??= _keyboardConfigurationService.CommandBindings.FirstOrDefault(c => c.Keys.AreKeysEqual(_appState.PreviousKeys));
if (key == Key.Escape)
{
var doGeneralReset = false;
if (_appState.PreviousKeys.Count > 1 || _appState.IsAllShortcutVisible || _appState.MessageBoxText != null)
{
doGeneralReset = true;
}
/*else if (_currentLocation.Container.CanHandleEscape)
{
var escapeResult = await _currentLocation.Container.HandleEscape();
if (escapeResult.NavigateTo != null)
{
setHandled(true);
_appState.PreviousKeys.Clear();
await _appState.SelectedTab.OpenContainer(escapeResult.NavigateTo);
}
else
{
if (escapeResult.Handled)
{
_appState.PreviousKeys.Clear();
}
else
{
doGeneralReset = true;
}
}
}*/
if (doGeneralReset)
{
setHandled(true);
_appState.IsAllShortcutVisible = false;
_appState.MessageBoxText = null;
_appState.PreviousKeys.Clear();
_appState.PossibleCommands = new();
}
}
else if (key == Key.Enter
&& _appState.MessageBoxText != null)
{
_appState.PreviousKeys.Clear();
//_dialogService.ProcessMessageBox();
setHandled(true);
}
else if (selectedCommandBinding != null)
{
setHandled(true);
_appState.PreviousKeys.Clear();
_appState.PossibleCommands = new();
await CallCommandAsync(selectedCommandBinding.Command);
}
else if (_keysToSkip.Any(k => k.AreKeysEqual(_appState.PreviousKeys)))
{
_appState.PreviousKeys.Clear();
_appState.PossibleCommands = new();
return;
}
else if (_appState.PreviousKeys.Count == 2)
{
setHandled(true);
_appState.NoCommandFound = true;
_appState.PreviousKeys.Clear();
_appState.PossibleCommands = new();
}
else
{
setHandled(true);
var possibleCommands = _keyboardConfigurationService.AllShortcut.Where(c => c.Keys[0].AreEquals(keyWithModifiers)).ToList();
if (possibleCommands.Count == 0)
{
_appState.NoCommandFound = true;
_appState.PreviousKeys.Clear();
}
else
{
_appState.PossibleCommands = possibleCommands;
}
}
}
private async Task CallCommandAsync(Commands command)
{
try
{
await _commandHandlerService.HandleCommandAsync(command);
}
catch (Exception e)
{
_logger.LogError(e, "Unknown error while running command. {Command} {Error}", command, e);
}
}
}
}

View File

@@ -0,0 +1,56 @@
using Avalonia.Input;
using FileTime.App.Core.Models.Enums;
using FileTime.GuiApp.Configuration;
using FileTime.GuiApp.Models;
using FileTime.GuiApp.ViewModels;
namespace FileTime.GuiApp.Services
{
public class KeyInputHandlerService : IKeyInputHandlerService
{
private readonly IGuiAppState _appState;
private readonly IDefaultModeKeyInputHandler _defaultModeKeyInputHandler;
private readonly IRapidTravelModeKeyInputHandler _rapidTravelModeKeyInputHandler;
public KeyInputHandlerService(
IGuiAppState appState,
IDefaultModeKeyInputHandler defaultModeKeyInputHandler,
IRapidTravelModeKeyInputHandler rapidTravelModeKeyInputHandler
)
{
_appState = appState;
_defaultModeKeyInputHandler = defaultModeKeyInputHandler;
_rapidTravelModeKeyInputHandler = rapidTravelModeKeyInputHandler;
}
public async Task ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled)
{
if (key == Key.LeftAlt
|| key == Key.RightAlt
|| key == Key.LeftShift
|| key == Key.RightShift
|| key == Key.LeftCtrl
|| key == Key.RightCtrl)
{
return;
}
//_appState.NoCommandFound = false;
var isAltPressed = (keyModifiers & KeyModifiers.Alt) == KeyModifiers.Alt;
var isShiftPressed = (keyModifiers & KeyModifiers.Shift) == KeyModifiers.Shift;
var isCtrlPressed = (keyModifiers & KeyModifiers.Control) == KeyModifiers.Control;
var specialKeyStatus = new SpecialKeysStatus(isAltPressed, isShiftPressed, isCtrlPressed);
if (_appState.ViewMode == ViewMode.Default)
{
await _defaultModeKeyInputHandler.HandleInputKey(key, specialKeyStatus, setHandled);
}
else
{
await _rapidTravelModeKeyInputHandler.HandleInputKey(key, specialKeyStatus, setHandled);
}
}
}
}

View File

@@ -0,0 +1,61 @@
using FileTime.App.Core.Command;
using FileTime.GuiApp.Configuration;
using Microsoft.Extensions.Options;
namespace FileTime.GuiApp.Services
{
public class KeyboardConfigurationService : IKeyboardConfigurationService
{
public IReadOnlyList<CommandBindingConfiguration> CommandBindings { get; }
public IReadOnlyList<CommandBindingConfiguration> UniversalCommandBindings { get; }
public IReadOnlyList<CommandBindingConfiguration> AllShortcut { get; }
public KeyboardConfigurationService(IOptions<KeyBindingConfiguration> keyBindingConfiguration)
{
var commandBindings = new List<CommandBindingConfiguration>();
var universalCommandBindings = new List<CommandBindingConfiguration>();
IEnumerable<CommandBindingConfiguration> keyBindings = keyBindingConfiguration.Value.KeyBindings;
if (keyBindingConfiguration.Value.UseDefaultBindings)
{
keyBindings = keyBindings.Concat(keyBindingConfiguration.Value.DefaultKeyBindings);
}
foreach (var keyBinding in keyBindings)
{
if (keyBinding.Command == Commands.None)
{
throw new FormatException($"No command is set in keybinding for keys '{keyBinding.KeysDisplayText}'");
}
else if (keyBinding.Keys.Count == 0)
{
throw new FormatException($"No keys set in keybinding for command '{keyBinding.Command}'.");
}
if (IsUniversal(keyBinding))
{
universalCommandBindings.Add(keyBinding);
}
else
{
commandBindings.Add(keyBinding);
}
}
CommandBindings = commandBindings.AsReadOnly();
UniversalCommandBindings = universalCommandBindings.AsReadOnly();
AllShortcut = new List<CommandBindingConfiguration>(CommandBindings.Concat(UniversalCommandBindings)).AsReadOnly();
}
private static bool IsUniversal(CommandBindingConfiguration keyMapping)
{
return keyMapping.Command == Commands.GoUp
|| keyMapping.Command == Commands.Open
|| keyMapping.Command == Commands.OpenOrRun
|| keyMapping.Command == Commands.MoveCursorUp
|| keyMapping.Command == Commands.MoveCursorDown
|| keyMapping.Command == Commands.MoveCursorUpPage
|| keyMapping.Command == Commands.MoveCursorDownPage;
}
}
}

View File

@@ -0,0 +1,88 @@
using Avalonia.Input;
using FileTime.GuiApp.Models;
namespace FileTime.GuiApp.Services
{
public class RapidTravelModeKeyInputHandler : IRapidTravelModeKeyInputHandler
{
public async Task HandleInputKey(Key key, SpecialKeysStatus specialKeysStatus, Action<bool> setHandled)
{
/*var keyString = key.ToString();
var updateRapidTravelFilter = false;
if (key == Key.Escape)
{
setHandled(true);
if (_appState.IsAllShortcutVisible)
{
_appState.IsAllShortcutVisible = false;
}
else if (_appState.MessageBoxText != null)
{
_appState.MessageBoxText = null;
}
else
{
await _appState.ExitRapidTravelMode();
}
}
else if (key == Key.Back)
{
if (_appState.RapidTravelText.Length > 0)
{
setHandled(true);
_appState.RapidTravelText = _appState.RapidTravelText.Substring(0, _appState.RapidTravelText.Length - 1);
updateRapidTravelFilter = true;
}
}
else if (keyString.Length == 1)
{
setHandled(true);
_appState.RapidTravelText += keyString.ToLower();
updateRapidTravelFilter = true;
}
else
{
var currentKeyAsList = new List<KeyConfig>() { new KeyConfig(key) };
var selectedCommandBinding = _keyboardConfigurationService.UniversalCommandBindings.FirstOrDefault(c => AreKeysEqual(c.Keys, currentKeyAsList));
if (selectedCommandBinding != null)
{
setHandled(true);
await CallCommandAsync(selectedCommandBinding.Command);
}
}
if (updateRapidTravelFilter)
{
var currentLocation = await _appState.SelectedTab.CurrentLocation.Container.WithoutVirtualContainer(MainPageViewModel.RAPIDTRAVEL);
var newLocation = new VirtualContainer(
currentLocation,
new List<Func<IEnumerable<IContainer>, IEnumerable<IContainer>>>()
{
container => container.Where(c => c.Name.ToLower().Contains(_appState.RapidTravelText))
},
new List<Func<IEnumerable<IElement>, IEnumerable<IElement>>>()
{
element => element.Where(e => e.Name.ToLower().Contains(_appState.RapidTravelText))
},
virtualContainerName: MainPageViewModel.RAPIDTRAVEL
);
await newLocation.Init();
await _appState.SelectedTab.OpenContainer(newLocation);
var selectedItemName = _appState.SelectedTab.SelectedItem?.Item.Name;
var currentLocationItems = await _appState.SelectedTab.CurrentLocation.GetItems();
if (currentLocationItems.FirstOrDefault(i => string.Equals(i.Item.Name, _appState.RapidTravelText, StringComparison.OrdinalIgnoreCase)) is IItemViewModel matchItem)
{
await _appState.SelectedTab.SetCurrentSelectedItem(matchItem.Item);
}
else if (!currentLocationItems.Select(i => i.Item.Name).Any(n => n == selectedItemName))
{
await _appState.SelectedTab.MoveCursorToFirst();
}
}*/
}
}
}

View File

@@ -5,6 +5,7 @@ using FileTime.App.Core;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Models; using FileTime.Core.Models;
using FileTime.Core.Services; using FileTime.Core.Services;
using FileTime.GuiApp.Services;
using FileTime.Providers.Local; using FileTime.Providers.Local;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@@ -17,6 +18,7 @@ namespace FileTime.GuiApp.ViewModels
[Inject(typeof(ILocalContentProvider), "_localContentProvider")] [Inject(typeof(ILocalContentProvider), "_localContentProvider")]
[Inject(typeof(IServiceProvider), PropertyName = "_serviceProvider")] [Inject(typeof(IServiceProvider), PropertyName = "_serviceProvider")]
[Inject(typeof(ILogger<MainWindowViewModel>), PropertyName = "_logger")] [Inject(typeof(ILogger<MainWindowViewModel>), PropertyName = "_logger")]
[Inject(typeof(IKeyInputHandlerService), PropertyName = "_keyInputHandlerService")]
public partial class MainWindowViewModel : IMainWindowViewModelBase public partial class MainWindowViewModel : IMainWindowViewModelBase
{ {
public bool Loading => false; public bool Loading => false;
@@ -51,6 +53,7 @@ namespace FileTime.GuiApp.ViewModels
public void ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled) public void ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled)
{ {
_keyInputHandlerService.ProcessKeyDown(key, keyModifiers, setHandled);
} }
} }
} }

View File

@@ -14,14 +14,11 @@
TransparencyLevelHint="Blur" TransparencyLevelHint="Blur"
Background="Transparent" Background="Transparent"
ExtendClientAreaToDecorationsHint="True" ExtendClientAreaToDecorationsHint="True"
x:DataType="vm:MainWindowViewModel" x:DataType="vm:IMainWindowViewModelBase"
x:CompileBindings="True"> x:CompileBindings="True">
<Design.DataContext>
<vm:MainWindowViewModel/>
</Design.DataContext>
<Grid Background="{DynamicResource AppBackgroundBrush}"> <Grid Background="{DynamicResource AppBackgroundBrush}">
<Grid IsVisible="{Binding Loading, Converter={x:Static BoolConverters.Not}}"> <Grid IsVisible="{Binding Loading, Converter={x:Static BoolConverters.Not}}" x:DataType="vm:MainWindowViewModel">
<Grid ColumnDefinitions="250,*" RowDefinitions="Auto,*"> <Grid ColumnDefinitions="250,*" RowDefinitions="Auto,*">
<Grid PointerPressed="HeaderPointerPressed"> <Grid PointerPressed="HeaderPointerPressed">
<Rectangle Fill="#01000000"/> <Rectangle Fill="#01000000"/>
@@ -32,9 +29,9 @@
<Rectangle Fill="#01000000"/> <Rectangle Fill="#01000000"/>
<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
Text="{Binding AppState.SelectedTab.CurrentSelectedItem^.DisplayNameText}" Foreground="{StaticResource AccentBrush}" /> Text="{Binding AppState.SelectedTab^.CurrentSelectedItem^.DisplayNameText}" Foreground="{StaticResource AccentBrush}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
@@ -89,7 +86,7 @@
<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 Width="40" Height="40" Source="{SvgImage /Assets/loading.svg}" Classes="LoadingAnimation"/>
</Grid> </Grid>
<ListBox <ListBox
@@ -98,7 +95,7 @@
x:CompileBindings="False" x:CompileBindings="False"
AutoScrollToSelectedItem="True" AutoScrollToSelectedItem="True"
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"> Classes="ContentListView">
@@ -117,7 +114,7 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
FontWeight="Bold" FontWeight="Bold"
Foreground="{DynamicResource ErrorBrush}" Foreground="{DynamicResource ErrorBrush}"
IsVisible="{Binding AppState.SelectedTab.CurrentLocation^.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}"> IsVisible="{Binding AppState.SelectedTab^.CurrentLocation^.Items^.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
Empty Empty
</TextBlock> </TextBlock>
</Grid> </Grid>

View File

@@ -19,5 +19,17 @@ namespace FileTime.Providers.Local
+ ((directoryInfo.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-"); + ((directoryInfo.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-");
} }
} }
private static IEnumerable<FileInfo> GetFilesSafe(DirectoryInfo directoryInfo)
{
try
{
return directoryInfo.GetFiles();
}
catch
{
return Enumerable.Empty<FileInfo>();
}
}
} }
} }

View File

@@ -34,9 +34,13 @@ namespace FileTime.Providers.Local
public override Task<IItem> GetItemByNativePathAsync(NativePath nativePath) public override Task<IItem> GetItemByNativePathAsync(NativePath nativePath)
{ {
var path = nativePath.Path; var path = nativePath.Path;
if (Directory.Exists(path)) if ((path?.Length ?? 0) == 0)
{ {
return Task.FromResult((IItem)DirectoryToContainer(new DirectoryInfo(path))); return Task.FromResult((IItem)this);
}
else if (Directory.Exists(path))
{
return Task.FromResult((IItem)DirectoryToContainer(new DirectoryInfo(path!.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar)));
} }
else if (File.Exists(path)) else if (File.Exists(path))
{ {
@@ -48,7 +52,7 @@ namespace FileTime.Providers.Local
public override Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName) => Task.FromResult(GetItemsByContainer(fullName)); public override Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName) => Task.FromResult(GetItemsByContainer(fullName));
public List<IAbsolutePath> GetItemsByContainer(FullName fullName) => GetItemsByContainer(new DirectoryInfo(GetNativePath(fullName).Path)); public List<IAbsolutePath> GetItemsByContainer(FullName fullName) => GetItemsByContainer(new DirectoryInfo(GetNativePath(fullName).Path));
public List<IAbsolutePath> GetItemsByContainer(DirectoryInfo directoryInfo) => directoryInfo.GetDirectories().Select(DirectoryToAbsolutePath).Concat(directoryInfo.GetFiles().Select(FileToAbsolutePath)).ToList(); public List<IAbsolutePath> GetItemsByContainer(DirectoryInfo directoryInfo) => directoryInfo.GetDirectories().Select(DirectoryToAbsolutePath).Concat(GetFilesSafe(directoryInfo).Select(FileToAbsolutePath)).ToList();
private IAbsolutePath DirectoryToAbsolutePath(DirectoryInfo directoryInfo) private IAbsolutePath DirectoryToAbsolutePath(DirectoryInfo directoryInfo)
{ {
@@ -65,12 +69,18 @@ namespace FileTime.Providers.Local
private Container DirectoryToContainer(DirectoryInfo directoryInfo) private Container DirectoryToContainer(DirectoryInfo directoryInfo)
{ {
var fullName = GetFullName(directoryInfo.FullName); var fullName = GetFullName(directoryInfo.FullName);
var parentFullName = fullName.GetParent();
var parent = new AbsolutePath(
this,
parentFullName ?? new FullName(""),
AbsolutePathType.Container);
return new( return new(
directoryInfo.Name, directoryInfo.Name,
directoryInfo.Name, directoryInfo.Name,
fullName, fullName,
new(directoryInfo.FullName), new(directoryInfo.FullName),
fullName.GetParent()!, parent,
(directoryInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden, (directoryInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden,
directoryInfo.Exists, directoryInfo.Exists,
directoryInfo.CreationTime, directoryInfo.CreationTime,
@@ -85,12 +95,15 @@ namespace FileTime.Providers.Local
private Element FileToElement(FileInfo fileInfo) private Element FileToElement(FileInfo fileInfo)
{ {
var fullName = GetFullName(fileInfo); var fullName = GetFullName(fileInfo);
var parentFullName = fullName.GetParent() ?? throw new Exception($"Path does not have parent: '{fileInfo.FullName}'");
var parent = new AbsolutePath(this, parentFullName, AbsolutePathType.Container);
return new( return new(
fileInfo.Name, fileInfo.Name,
fileInfo.Name, fileInfo.Name,
fullName, fullName,
new(fileInfo.FullName), new(fileInfo.FullName),
fullName.GetParent()!, parent,
(fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden, (fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden,
fileInfo.Exists, fileInfo.Exists,
fileInfo.CreationTime, fileInfo.CreationTime,