File scoped namespace

This commit is contained in:
2022-05-07 19:40:54 +02:00
parent b161ded92e
commit 9bf95ebe4e
126 changed files with 2562 additions and 2598 deletions

View File

@@ -1,2 +1,4 @@
[*.cs] [*.cs]
dotnet_diagnostic.RCS1196.severity = none dotnet_diagnostic.RCS1196.severity = none
csharp_style_namespace_declarations = file_scoped:error
dotnet_diagnostic.IDE0161.severity = error

View File

@@ -1,66 +1,65 @@
namespace FileTime.App.Core.Command namespace FileTime.App.Core.Command;
{
public enum Commands
{
None,
AutoRefresh, public enum Commands
ChangeTimelineMode, {
CloseTab, None,
Compress,
Copy, AutoRefresh,
CopyHash, ChangeTimelineMode,
CopyPath, CloseTab,
CreateContainer, Compress,
CreateElement, Copy,
Cut, CopyHash,
Edit, CopyPath,
EnterRapidTravel, CreateContainer,
FindByName, CreateElement,
FindByNameRegex, Cut,
GoToHome, Edit,
GoToPath, EnterRapidTravel,
GoToProvider, FindByName,
GoToRoot, FindByNameRegex,
GoUp, GoToHome,
HardDelete, GoToPath,
Mark, GoToProvider,
MoveCursorDown, GoToRoot,
MoveCursorDownPage, GoUp,
MoveCursorUp, HardDelete,
MoveCursorUpPage, Mark,
MoveToFirst, MoveCursorDown,
MoveToLast, MoveCursorDownPage,
NextTimelineBlock, MoveCursorUp,
NextTimelineCommand, MoveCursorUpPage,
Open, MoveToFirst,
OpenInFileBrowser, MoveToLast,
OpenOrRun, NextTimelineBlock,
PasteMerge, NextTimelineCommand,
PasteOverwrite, Open,
PasteSkip, OpenInFileBrowser,
PinFavorite, OpenOrRun,
PreviousTimelineBlock, PasteMerge,
PreviousTimelineCommand, PasteOverwrite,
Refresh, PasteSkip,
Rename, PinFavorite,
RunCommand, PreviousTimelineBlock,
ScanContainerSize, PreviousTimelineCommand,
ShowAllShotcut, Refresh,
SoftDelete, Rename,
SwitchToLastTab, RunCommand,
SwitchToTab1, ScanContainerSize,
SwitchToTab2, ShowAllShotcut,
SwitchToTab3, SoftDelete,
SwitchToTab4, SwitchToLastTab,
SwitchToTab5, SwitchToTab1,
SwitchToTab6, SwitchToTab2,
SwitchToTab7, SwitchToTab3,
SwitchToTab8, SwitchToTab4,
TimelinePause, SwitchToTab5,
TimelineRefresh, SwitchToTab6,
TimelineStart, SwitchToTab7,
ToggleAdvancedIcons, SwitchToTab8,
ToggleHidden, TimelinePause,
} TimelineRefresh,
TimelineStart,
ToggleAdvancedIcons,
ToggleHidden,
} }

View File

@@ -6,6 +6,10 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Reactive" Version="5.0.0" /> <PackageReference Include="System.Reactive" Version="5.0.0" />
</ItemGroup> </ItemGroup>

View File

@@ -1,26 +1,25 @@
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using DynamicData; using DynamicData;
namespace FileTime.App.Core.Models namespace FileTime.App.Core.Models;
public class BindedCollection<T> : IDisposable
{ {
public class BindedCollection<T> : IDisposable private readonly IDisposable _disposable;
public ReadOnlyObservableCollection<T> Collection { get; }
public BindedCollection(IObservable<IChangeSet<T>> dynamicList)
{ {
private readonly IDisposable _disposable; _disposable = dynamicList
public ReadOnlyObservableCollection<T> Collection { get; } .Bind(out var collection)
public BindedCollection(IObservable<IChangeSet<T>> dynamicList) .DisposeMany()
{ .Subscribe();
_disposable = dynamicList
.Bind(out var collection)
.DisposeMany()
.Subscribe();
Collection = collection; Collection = collection;
} }
public void Dispose() public void Dispose()
{ {
_disposable.Dispose(); _disposable.Dispose();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
}
} }
} }

View File

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

View File

@@ -1,12 +1,11 @@
namespace FileTime.App.Core.Models.Enums namespace FileTime.App.Core.Models.Enums;
public enum ItemViewMode
{ {
public enum ItemViewMode Default,
{ Alternative,
Default, Selected,
Alternative, Marked,
Selected, MarkedSelected,
Marked, MarkedAlternative
MarkedSelected,
MarkedAlternative
}
} }

View File

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

View File

@@ -1,14 +1,13 @@
namespace FileTime.App.Core.Models namespace FileTime.App.Core.Models;
{
public class ItemNamePart
{
public string Text { get; set; }
public bool IsSpecial { get; set; }
public ItemNamePart(string text, bool isSpecial = false) public class ItemNamePart
{ {
Text = text; public string Text { get; set; }
IsSpecial = isSpecial; public bool IsSpecial { get; set; }
}
public ItemNamePart(string text, bool isSpecial = false)
{
Text = text;
IsSpecial = isSpecial;
} }
} }

View File

@@ -1,16 +1,15 @@
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.App.Core.Services namespace FileTime.App.Core.Services;
{
public interface IClipboardService
{
Type? CommandType { get; }
IReadOnlyList<IAbsolutePath> Content { get; }
void AddContent(IAbsolutePath absolutePath); public interface IClipboardService
void RemoveContent(IAbsolutePath absolutePath); {
void Clear(); Type? CommandType { get; }
void SetCommand<T>() where T : ITransportationCommand; IReadOnlyList<IAbsolutePath> Content { get; }
}
void AddContent(IAbsolutePath absolutePath);
void RemoveContent(IAbsolutePath absolutePath);
void Clear();
void SetCommand<T>() where T : ITransportationCommand;
} }

View File

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

View File

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

View File

@@ -1,11 +1,10 @@
using FileTime.App.Core.Models; using FileTime.App.Core.Models;
namespace FileTime.App.Core.Services namespace FileTime.App.Core.Services;
public interface IItemNameConverterService
{ {
public interface IItemNameConverterService List<ItemNamePart> GetDisplayName(string name, string? searchText);
{ string GetFileExtension(string fullName);
List<ItemNamePart> GetDisplayName(string name, string? searchText); string GetFileName(string fullName);
string GetFileExtension(string fullName);
string GetFileName(string fullName);
}
} }

View File

@@ -1,10 +1,9 @@
using System.Reactive.Concurrency; using System.Reactive.Concurrency;
namespace FileTime.App.Core.Services namespace FileTime.App.Core.Services;
public interface IRxSchedulerService
{ {
public interface IRxSchedulerService IScheduler GetWorkerScheduler();
{ IScheduler GetUIScheduler();
IScheduler GetWorkerScheduler();
IScheduler GetUIScheduler();
}
} }

View File

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

View File

@@ -1,10 +1,9 @@
using FileTime.Core.Models; using FileTime.Core.Models;
using InitableService; using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
public interface IContainerSizeContainerViewModel : IItemViewModel, IInitable<IContainer, ITabViewModel>
{ {
public interface IContainerSizeContainerViewModel : IItemViewModel, IInitable<IContainer, ITabViewModel> long Size { get; set; }
{
long Size { get; set; }
}
} }

View File

@@ -1,10 +1,9 @@
using FileTime.Core.Models; using FileTime.Core.Models;
using InitableService; using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
public interface IContainerViewModel : IItemViewModel, IInitable<IContainer, ITabViewModel>
{ {
public interface IContainerViewModel : IItemViewModel, IInitable<IContainer, ITabViewModel> IContainer? Container { get; }
{
IContainer? Container { get; }
}
} }

View File

@@ -1,10 +1,9 @@
using FileTime.Core.Models; using FileTime.Core.Models;
using InitableService; using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
public interface IElementViewModel : IItemViewModel, IInitable<IElement, ITabViewModel>
{ {
public interface IElementViewModel : IItemViewModel, IInitable<IElement, ITabViewModel> long? Size { get; set; }
{
long? Size { get; set; }
}
} }

View File

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

View File

@@ -3,19 +3,18 @@ using FileTime.App.Core.Models.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
using InitableService; using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
public interface IItemViewModel : IInitable<IItem, ITabViewModel>
{ {
public interface IItemViewModel : IInitable<IItem, ITabViewModel> IItem? BaseItem { get; set; }
{ IObservable<IReadOnlyList<ItemNamePart>>? DisplayName { get; set; }
IItem? BaseItem { get; set; } string? DisplayNameText { get; set; }
IObservable<IReadOnlyList<ItemNamePart>>? DisplayName { get; set; } IObservable<bool>? IsSelected { get; set; }
string? DisplayNameText { get; set; } IObservable<bool>? IsMarked { get; set; }
IObservable<bool>? IsSelected { get; set; } IObservable<bool> IsAlternative { get; }
IObservable<bool>? IsMarked { get; set; } IObservable<ItemViewMode> ViewMode { get; set; }
IObservable<bool> IsAlternative { get; } DateTime? CreatedAt { get; set; }
IObservable<ItemViewMode> ViewMode { get; set; } string? Attributes { get; set; }
DateTime? CreatedAt { get; set; } bool EqualsTo(IItemViewModel? itemViewModel);
string? Attributes { get; set; }
bool EqualsTo(IItemViewModel? itemViewModel);
}
} }

View File

@@ -5,26 +5,25 @@ using FileTime.Core.Models;
using FileTime.Core.Services; using FileTime.Core.Services;
using InitableService; using InitableService;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
public interface ITabViewModel : IInitable<ITab, int>
{ {
public interface ITabViewModel : IInitable<ITab, int> ITab? Tab { get; }
{ int TabNumber { get; }
ITab? Tab { get; } IObservable<bool> IsSelected { get; }
int TabNumber { get; } IObservable<IContainer?> CurrentLocation { get; }
IObservable<bool> IsSelected { get; } IObservable<IItemViewModel?> CurrentSelectedItem { get; }
IObservable<IContainer?> CurrentLocation { get; } IObservable<IObservable<IChangeSet<IItemViewModel>>?> CurrentItems { get; }
IObservable<IItemViewModel?> CurrentSelectedItem { get; } IObservable<IChangeSet<IAbsolutePath>> MarkedItems { get; }
IObservable<IObservable<IChangeSet<IItemViewModel>>?> CurrentItems { get; } IObservable<IObservable<IChangeSet<IItemViewModel>>?> SelectedsChildren { get; }
IObservable<IChangeSet<IAbsolutePath>> MarkedItems { get; } IObservable<IObservable<IChangeSet<IItemViewModel>>?> ParentsChildren { get; }
IObservable<IObservable<IChangeSet<IItemViewModel>>?> SelectedsChildren { get; } BindedCollection<IItemViewModel>? CurrentItemsCollection { get; }
IObservable<IObservable<IChangeSet<IItemViewModel>>?> ParentsChildren { get; } BindedCollection<IItemViewModel>? SelectedsChildrenCollection { get; }
BindedCollection<IItemViewModel>? CurrentItemsCollection { get; } BindedCollection<IItemViewModel>? ParentsChildrenCollection { get; }
BindedCollection<IItemViewModel>? SelectedsChildrenCollection { get; } IObservable<IReadOnlyCollection<IItemViewModel>?> CurrentItemsCollectionObservable { get; }
BindedCollection<IItemViewModel>? ParentsChildrenCollection { get; } void ClearMarkedItems();
IObservable<IReadOnlyCollection<IItemViewModel>?> CurrentItemsCollectionObservable { get; } void RemoveMarkedItem(IAbsolutePath item);
void ClearMarkedItems(); void AddMarkedItem(IAbsolutePath item);
void RemoveMarkedItem(IAbsolutePath item); void ToggleMarkedItem(IAbsolutePath item);
void AddMarkedItem(IAbsolutePath item);
void ToggleMarkedItem(IAbsolutePath item);
}
} }

View File

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

View File

@@ -1,14 +1,13 @@
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.App.Core.Extensions namespace FileTime.App.Core.Extensions;
public static class ViewModelExtensions
{ {
public static class ViewModelExtensions public static IAbsolutePath ToAbsolutePath(this IItemViewModel itemViewModel)
{ {
public static IAbsolutePath ToAbsolutePath(this IItemViewModel itemViewModel) var item = itemViewModel.BaseItem ?? throw new ArgumentException($"{nameof(itemViewModel)} does not have {nameof(IItemViewModel.BaseItem)}");
{ return new AbsolutePath(item);
var item = itemViewModel.BaseItem ?? throw new ArgumentException($"{nameof(itemViewModel)} does not have {nameof(IItemViewModel.BaseItem)}");
return new AbsolutePath(item);
}
} }
} }

View File

@@ -6,6 +6,10 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<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" />

View File

@@ -1,51 +1,50 @@
using FileTime.Core.Command; using FileTime.Core.Command;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.App.Core.Services namespace FileTime.App.Core.Services;
public class ClipboardService : IClipboardService
{ {
public class ClipboardService : IClipboardService private List<IAbsolutePath> _content;
public IReadOnlyList<IAbsolutePath> Content { get; private set; }
public Type? CommandType { get; private set; }
public ClipboardService()
{ {
private List<IAbsolutePath> _content; _content = new List<IAbsolutePath>();
public IReadOnlyList<IAbsolutePath> Content { get; private set; } Content = _content.AsReadOnly();
public Type? CommandType { get; private set; } }
public ClipboardService() public void AddContent(IAbsolutePath absolutePath)
{
foreach (var content in _content)
{ {
_content = new List<IAbsolutePath>(); if (content.Equals(absolutePath)) return;
Content = _content.AsReadOnly();
} }
public void AddContent(IAbsolutePath absolutePath) _content.Add(absolutePath);
}
public void RemoveContent(IAbsolutePath absolutePath)
{
for (var i = 0; i < _content.Count; i++)
{ {
foreach (var content in _content) if (_content[i].Equals(absolutePath))
{ {
if (content.Equals(absolutePath)) return; _content.RemoveAt(i--);
} }
_content.Add(absolutePath);
}
public void RemoveContent(IAbsolutePath absolutePath)
{
for (var i = 0; i < _content.Count; i++)
{
if (_content[i].Equals(absolutePath))
{
_content.RemoveAt(i--);
}
}
}
public void Clear()
{
_content = new List<IAbsolutePath>();
Content = _content.AsReadOnly();
CommandType = null;
}
public void SetCommand<T>() where T : ITransportationCommand
{
CommandType = typeof(T);
} }
} }
public void Clear()
{
_content = new List<IAbsolutePath>();
Content = _content.AsReadOnly();
CommandType = null;
}
public void SetCommand<T>() where T : ITransportationCommand
{
CommandType = typeof(T);
}
} }

View File

@@ -4,51 +4,50 @@ using FileTime.App.Core.Command;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.App.Core.Services.CommandHandler namespace FileTime.App.Core.Services.CommandHandler;
public abstract class CommandHandlerBase : ICommandHandler
{ {
public abstract class CommandHandlerBase : ICommandHandler private readonly Dictionary<Commands, Func<Task>> _commandHandlers = new();
private readonly IAppState? _appState;
protected CommandHandlerBase(IAppState? appState = null)
{ {
private readonly Dictionary<Commands, Func<Task>> _commandHandlers = new(); _appState = appState;
private readonly IAppState? _appState; }
protected CommandHandlerBase(IAppState? appState = null) public bool CanHandleCommand(Commands command) => _commandHandlers.ContainsKey(command);
public async Task HandleCommandAsync(Commands command) => await _commandHandlers[command].Invoke();
protected void AddCommandHandler(Commands command, Func<Task> handler) => _commandHandlers.Add(command, handler);
protected void AddCommandHandlers(IEnumerable<(Commands command, Func<Task> handler)> commandHandlers)
{
foreach (var (command, handler) in commandHandlers)
{ {
_appState = appState; AddCommandHandler(command, handler);
}
public bool CanHandleCommand(Commands command) => _commandHandlers.ContainsKey(command);
public async Task HandleCommandAsync(Commands command) => await _commandHandlers[command].Invoke();
protected void AddCommandHandler(Commands command, Func<Task> handler) => _commandHandlers.Add(command, handler);
protected void AddCommandHandlers(IEnumerable<(Commands command, Func<Task> handler)> commandHandlers)
{
foreach (var (command, handler) in commandHandlers)
{
AddCommandHandler(command, handler);
}
}
protected void RemoveCommandHandler(Commands command) => _commandHandlers.Remove(command);
protected IDisposable SaveSelectedTab(Action<ITabViewModel?> handler) => RunWithAppState(appState => appState.SelectedTab.Subscribe(handler));
protected IDisposable SaveCurrentSelectedItem(Action<IItemViewModel?> handler)
=> RunWithAppState(appState => appState.SelectedTab.Select(t => t == null ? Observable.Return<IItemViewModel?>(null) : t.CurrentSelectedItem).Switch().Subscribe(handler));
protected IDisposable SaveCurrentLocation(Action<IContainer?> handler)
=> RunWithAppState(appState => appState.SelectedTab.Select(t => t == null ? Observable.Return<IContainer?>(null) : t.CurrentLocation).Switch().Subscribe(handler));
protected IDisposable SaveCurrentItems(Action<IEnumerable<IItemViewModel>> handler)
=> RunWithAppState(appState => appState.SelectedTab.Select(t => t?.CurrentItemsCollectionObservable ?? Observable.Return((IEnumerable<IItemViewModel>?)Enumerable.Empty<IItemViewModel>())).Switch().Subscribe(i => handler(i ?? Enumerable.Empty<IItemViewModel>())));
protected IDisposable SaveMarkedItems(Action<IChangeSet<IAbsolutePath>> handler)
=> RunWithAppState(appstate => appstate.SelectedTab.Select(t => t == null ? Observable.Empty<IChangeSet<IAbsolutePath>>() : t.MarkedItems).Switch().Subscribe(handler));
private IDisposable RunWithAppState(Func<IAppState, IDisposable> act)
{
if (_appState == null) throw new NullReferenceException($"AppState is nit initialized in {nameof(CommandHandlerBase)}.");
return act(_appState);
} }
} }
protected void RemoveCommandHandler(Commands command) => _commandHandlers.Remove(command);
protected IDisposable SaveSelectedTab(Action<ITabViewModel?> handler) => RunWithAppState(appState => appState.SelectedTab.Subscribe(handler));
protected IDisposable SaveCurrentSelectedItem(Action<IItemViewModel?> handler)
=> RunWithAppState(appState => appState.SelectedTab.Select(t => t == null ? Observable.Return<IItemViewModel?>(null) : t.CurrentSelectedItem).Switch().Subscribe(handler));
protected IDisposable SaveCurrentLocation(Action<IContainer?> handler)
=> RunWithAppState(appState => appState.SelectedTab.Select(t => t == null ? Observable.Return<IContainer?>(null) : t.CurrentLocation).Switch().Subscribe(handler));
protected IDisposable SaveCurrentItems(Action<IEnumerable<IItemViewModel>> handler)
=> RunWithAppState(appState => appState.SelectedTab.Select(t => t?.CurrentItemsCollectionObservable ?? Observable.Return((IEnumerable<IItemViewModel>?)Enumerable.Empty<IItemViewModel>())).Switch().Subscribe(i => handler(i ?? Enumerable.Empty<IItemViewModel>())));
protected IDisposable SaveMarkedItems(Action<IChangeSet<IAbsolutePath>> handler)
=> RunWithAppState(appstate => appstate.SelectedTab.Select(t => t == null ? Observable.Empty<IChangeSet<IAbsolutePath>>() : t.MarkedItems).Switch().Subscribe(handler));
private IDisposable RunWithAppState(Func<IAppState, IDisposable> act)
{
if (_appState == null) throw new NullReferenceException($"AppState is nit initialized in {nameof(CommandHandlerBase)}.");
return act(_appState);
}
} }

View File

@@ -6,91 +6,90 @@ using FileTime.Core.Command;
using FileTime.Core.Command.Copy; using FileTime.Core.Command.Copy;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.App.Core.Services.CommandHandler namespace FileTime.App.Core.Services.CommandHandler;
public class ItemManipulationCommandHandler : CommandHandlerBase
{ {
public class ItemManipulationCommandHandler : CommandHandlerBase private ITabViewModel? _selectedTab;
private IItemViewModel? _currentSelectedItem;
private readonly ICommandHandlerService _commandHandlerService;
private readonly IClipboardService _clipboardService;
private BindedCollection<IAbsolutePath>? _markedItems;
public ItemManipulationCommandHandler(
IAppState appState,
ICommandHandlerService commandHandlerService,
IClipboardService clipboardService) : base(appState)
{ {
private ITabViewModel? _selectedTab; _commandHandlerService = commandHandlerService;
private IItemViewModel? _currentSelectedItem; _clipboardService = clipboardService;
private readonly ICommandHandlerService _commandHandlerService;
private readonly IClipboardService _clipboardService;
private BindedCollection<IAbsolutePath>? _markedItems;
public ItemManipulationCommandHandler( SaveSelectedTab(t =>
IAppState appState,
ICommandHandlerService commandHandlerService,
IClipboardService clipboardService) : base(appState)
{ {
_commandHandlerService = commandHandlerService; _selectedTab = t;
_clipboardService = clipboardService; _markedItems?.Dispose();
_markedItems = t == null ? null : new BindedCollection<IAbsolutePath>(t.MarkedItems);
});
SaveCurrentSelectedItem(i => _currentSelectedItem = i);
SaveSelectedTab(t => AddCommandHandlers(new (Commands, Func<Task>)[]
{
_selectedTab = t;
_markedItems?.Dispose();
_markedItems = t == null ? null : new BindedCollection<IAbsolutePath>(t.MarkedItems);
});
SaveCurrentSelectedItem(i => _currentSelectedItem = i);
AddCommandHandlers(new (Commands, Func<Task>)[]
{
(Commands.Copy, Copy),
(Commands.Mark, MarkItem),
(Commands.PasteMerge, PasteMerge),
(Commands.PasteOverwrite, PasteOverwrite),
(Commands.PasteSkip, PasteSkip),
});
}
private async Task MarkItem()
{ {
if (_selectedTab == null || _currentSelectedItem?.BaseItem?.FullName == null) return; (Commands.Copy, Copy),
(Commands.Mark, MarkItem),
(Commands.PasteMerge, PasteMerge),
(Commands.PasteOverwrite, PasteOverwrite),
(Commands.PasteSkip, PasteSkip),
});
}
_selectedTab.ToggleMarkedItem(new AbsolutePath(_currentSelectedItem.BaseItem)); private async Task MarkItem()
await _commandHandlerService.HandleCommandAsync(Commands.MoveCursorDown); {
} if (_selectedTab == null || _currentSelectedItem?.BaseItem?.FullName == null) return;
private Task Copy() _selectedTab.ToggleMarkedItem(new AbsolutePath(_currentSelectedItem.BaseItem));
await _commandHandlerService.HandleCommandAsync(Commands.MoveCursorDown);
}
private Task Copy()
{
_clipboardService.Clear();
_clipboardService.SetCommand<CopyCommand>();
if ((_markedItems?.Collection.Count ?? 0) > 0)
{ {
_clipboardService.Clear(); foreach (var item in _markedItems!.Collection)
_clipboardService.SetCommand<CopyCommand>();
if ((_markedItems?.Collection.Count ?? 0) > 0)
{ {
foreach (var item in _markedItems!.Collection) _clipboardService.AddContent(item);
{
_clipboardService.AddContent(item);
}
_selectedTab?.ClearMarkedItems();
}
else if (_currentSelectedItem?.BaseItem != null)
{
_clipboardService.AddContent(new AbsolutePath(_currentSelectedItem.BaseItem));
} }
return Task.CompletedTask; _selectedTab?.ClearMarkedItems();
}
else if (_currentSelectedItem?.BaseItem != null)
{
_clipboardService.AddContent(new AbsolutePath(_currentSelectedItem.BaseItem));
} }
private async Task PasteMerge() return Task.CompletedTask;
{ }
await Paste(TransportMode.Merge);
}
private async Task PasteOverwrite() private async Task PasteMerge()
{ {
await Paste(TransportMode.Overwrite); await Paste(TransportMode.Merge);
} }
private async Task PasteSkip() private async Task PasteOverwrite()
{ {
await Paste(TransportMode.Skip); await Paste(TransportMode.Overwrite);
} }
private Task Paste(TransportMode skip) private async Task PasteSkip()
{ {
if (_clipboardService.CommandType is null) return Task.CompletedTask; await Paste(TransportMode.Skip);
return Task.CompletedTask; }
}
private Task Paste(TransportMode skip)
{
if (_clipboardService.CommandType is null) return Task.CompletedTask;
return Task.CompletedTask;
} }
} }

View File

@@ -4,65 +4,64 @@ using FileTime.App.Core.Extensions;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.App.Core.Services.CommandHandler namespace FileTime.App.Core.Services.CommandHandler;
public class NavigationCommandHandler : CommandHandlerBase
{ {
public class NavigationCommandHandler : CommandHandlerBase private ITabViewModel? _selectedTab;
private IContainer? _currentLocation;
private IItemViewModel? _currentSelectedItem;
private IEnumerable<IItemViewModel> _currentItems = Enumerable.Empty<IItemViewModel>();
public NavigationCommandHandler(IAppState appState) : base(appState)
{ {
private ITabViewModel? _selectedTab; SaveSelectedTab(t => _selectedTab = t);
private IContainer? _currentLocation; SaveCurrentSelectedItem(i => _currentSelectedItem = i);
private IItemViewModel? _currentSelectedItem; SaveCurrentLocation(l => _currentLocation = l);
private IEnumerable<IItemViewModel> _currentItems = Enumerable.Empty<IItemViewModel>(); SaveCurrentItems(i => _currentItems = i);
public NavigationCommandHandler(IAppState appState) : base(appState) AddCommandHandlers(new (Commands, Func<Task>)[]
{ {
SaveSelectedTab(t => _selectedTab = t); (Commands.GoUp, GoUp),
SaveCurrentSelectedItem(i => _currentSelectedItem = i); (Commands.MoveCursorDown, MoveCursorDown),
SaveCurrentLocation(l => _currentLocation = l); (Commands.MoveCursorUp, MoveCursorUp),
SaveCurrentItems(i => _currentItems = i); (Commands.Open, OpenContainer),
});
}
AddCommandHandlers(new (Commands, Func<Task>)[] private Task OpenContainer()
{ {
(Commands.GoUp, GoUp), if (_currentSelectedItem is not IContainerViewModel containerViewModel || containerViewModel.Container is null) return Task.CompletedTask;
(Commands.MoveCursorDown, MoveCursorDown),
(Commands.MoveCursorUp, MoveCursorUp),
(Commands.Open, OpenContainer),
});
}
private Task OpenContainer() _selectedTab?.Tab?.SetCurrentLocation(containerViewModel.Container);
{ return Task.CompletedTask;
if (_currentSelectedItem is not IContainerViewModel containerViewModel || containerViewModel.Container is null) return Task.CompletedTask; }
_selectedTab?.Tab?.SetCurrentLocation(containerViewModel.Container); private async Task GoUp()
return Task.CompletedTask; {
} if (_currentLocation?.Parent is not IAbsolutePath parentPath || await parentPath.ResolveAsyncSafe() is not IContainer newContainer) return;
_selectedTab?.Tab?.SetCurrentLocation(newContainer);
}
private async Task GoUp() private Task MoveCursorDown()
{ {
if (_currentLocation?.Parent is not IAbsolutePath parentPath || await parentPath.ResolveAsyncSafe() is not IContainer newContainer) return; SelectNewSelectedItem(i => i.SkipWhile(i => !i.EqualsTo(_currentSelectedItem)).Skip(1).FirstOrDefault());
_selectedTab?.Tab?.SetCurrentLocation(newContainer); return Task.CompletedTask;
} }
private Task MoveCursorDown() private Task MoveCursorUp()
{ {
SelectNewSelectedItem(i => i.SkipWhile(i => !i.EqualsTo(_currentSelectedItem)).Skip(1).FirstOrDefault()); SelectNewSelectedItem(i => i.TakeWhile(i => !i.EqualsTo(_currentSelectedItem)).LastOrDefault());
return Task.CompletedTask; return Task.CompletedTask;
} }
private Task MoveCursorUp() private void SelectNewSelectedItem(Func<IEnumerable<IItemViewModel>, IItemViewModel?> getNewSelected)
{ {
SelectNewSelectedItem(i => i.TakeWhile(i => !i.EqualsTo(_currentSelectedItem)).LastOrDefault()); if (_selectedTab is null || _currentLocation is null) return;
return Task.CompletedTask;
}
private void SelectNewSelectedItem(Func<IEnumerable<IItemViewModel>, IItemViewModel?> getNewSelected) var newSelectedItem = getNewSelected(_currentItems);
{ if (newSelectedItem == null) return;
if (_selectedTab is null || _currentLocation is null) return;
var newSelectedItem = getNewSelected(_currentItems); _selectedTab.Tab?.SetSelectedItem(newSelectedItem.ToAbsolutePath());
if (newSelectedItem == null) return;
_selectedTab.Tab?.SetSelectedItem(newSelectedItem.ToAbsolutePath());
}
} }
} }

View File

@@ -1,80 +1,79 @@
using FileTime.App.Core.Command; using FileTime.App.Core.Command;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace FileTime.App.Core.Services namespace FileTime.App.Core.Services;
public class CommandHandlerService : ICommandHandlerService
{ {
public class CommandHandlerService : ICommandHandlerService private readonly Lazy<IEnumerable<ICommandHandler>> _commandHandlers;
public CommandHandlerService(IServiceProvider serviceProvider)
{ {
private readonly Lazy<IEnumerable<ICommandHandler>> _commandHandlers; _commandHandlers = new Lazy<IEnumerable<ICommandHandler>>(() => serviceProvider.GetServices<ICommandHandler>());
public CommandHandlerService(IServiceProvider serviceProvider) //(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.HardDelete, HardDelete),
//(Commands.Mark, MarkCurrentItem),
//(Commands.MoveCursorDownPage, MoveCursorDownPage),
//(Commands.MoveCursorUpPage, MoveCursorUpPage),
//(Commands.MoveToFirst, MoveToFirst),
//(Commands.MoveToLast, MoveToLast),
//(Commands.NextTimelineBlock, SelectNextTimelineBlock),
//(Commands.NextTimelineCommand, SelectNextTimelineCommand),
//(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)
{
var handler = _commandHandlers.Value.FirstOrDefault(h => h.CanHandleCommand(command));
if (handler != null)
{ {
_commandHandlers = new Lazy<IEnumerable<ICommandHandler>>(() => serviceProvider.GetServices<ICommandHandler>()); await handler.HandleCommandAsync(command);
//(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.HardDelete, HardDelete),
//(Commands.Mark, MarkCurrentItem),
//(Commands.MoveCursorDownPage, MoveCursorDownPage),
//(Commands.MoveCursorUpPage, MoveCursorUpPage),
//(Commands.MoveToFirst, MoveToFirst),
//(Commands.MoveToLast, MoveToLast),
//(Commands.NextTimelineBlock, SelectNextTimelineBlock),
//(Commands.NextTimelineCommand, SelectNextTimelineCommand),
//(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)
{
var handler = _commandHandlers.Value.FirstOrDefault(h => h.CanHandleCommand(command));
if (handler != null)
{
await handler.HandleCommandAsync(command);
}
} }
} }
} }

View File

@@ -1,56 +1,55 @@
using FileTime.App.Core.Models; using FileTime.App.Core.Models;
namespace FileTime.App.Core.Services namespace FileTime.App.Core.Services;
public class ItemNameConverterService : IItemNameConverterService
{ {
public class ItemNameConverterService : IItemNameConverterService public List<ItemNamePart> GetDisplayName(string name, string? searchText)
{ {
public List<ItemNamePart> GetDisplayName(string name, string? searchText) var nameParts = new List<ItemNamePart>();
searchText = searchText?.ToLower();
if (!string.IsNullOrEmpty(searchText))
{ {
var nameParts = new List<ItemNamePart>(); var nameLeft = name;
searchText = searchText?.ToLower();
if (!string.IsNullOrEmpty(searchText)) while (nameLeft.ToLower().IndexOf(searchText, StringComparison.Ordinal) is int rapidTextStart && rapidTextStart != -1)
{ {
var nameLeft = name; var before = rapidTextStart > 0 ? nameLeft.Substring(0, rapidTextStart) : null;
var rapidTravel = nameLeft.Substring(rapidTextStart, searchText.Length);
while (nameLeft.ToLower().IndexOf(searchText, StringComparison.Ordinal) is int rapidTextStart && rapidTextStart != -1) nameLeft = nameLeft.Substring(rapidTextStart + searchText.Length);
if (before != null)
{ {
var before = rapidTextStart > 0 ? nameLeft.Substring(0, rapidTextStart) : null; nameParts.Add(new ItemNamePart(before));
var rapidTravel = nameLeft.Substring(rapidTextStart, searchText.Length);
nameLeft = nameLeft.Substring(rapidTextStart + searchText.Length);
if (before != null)
{
nameParts.Add(new ItemNamePart(before));
}
nameParts.Add(new ItemNamePart(rapidTravel, true));
} }
if (nameLeft.Length > 0) nameParts.Add(new ItemNamePart(rapidTravel, true));
{
nameParts.Add(new ItemNamePart(nameLeft));
}
} }
else
if (nameLeft.Length > 0)
{ {
nameParts.Add(new ItemNamePart(name)); nameParts.Add(new ItemNamePart(nameLeft));
} }
return nameParts;
} }
else
public string GetFileName(string fullName)
{ {
var parts = fullName.Split('.'); nameParts.Add(new ItemNamePart(name));
var fileName = string.Join('.', parts[..^1]);
return string.IsNullOrEmpty(fileName) ? fullName : fileName;
}
public string GetFileExtension(string fullName)
{
var parts = fullName.Split('.');
return parts.Length == 1 || (parts.Length == 2 && string.IsNullOrEmpty(parts[0])) ? "" : parts[^1];
} }
return nameParts;
} }
}
public string GetFileName(string fullName)
{
var parts = fullName.Split('.');
var fileName = string.Join('.', parts[..^1]);
return string.IsNullOrEmpty(fileName) ? fullName : fileName;
}
public string GetFileExtension(string fullName)
{
var parts = fullName.Split('.');
return parts.Length == 1 || (parts.Length == 2 && string.IsNullOrEmpty(parts[0])) ? "" : parts[^1];
}
}

View File

@@ -3,27 +3,26 @@ using FileTime.App.Core.Services.CommandHandler;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace FileTime.App.Core namespace FileTime.App.Core;
{
public static class Startup
{
public static IServiceCollection AddCoreAppServices(this IServiceCollection serviceCollection)
{
return serviceCollection
.AddTransient<ITabViewModel, TabViewModel>()
.AddTransient<IContainerViewModel, ContainerViewModel>()
.AddTransient<IElementViewModel, ElementViewModel>()
.AddTransient<IItemNameConverterService, ItemNameConverterService>()
.AddSingleton<ICommandHandlerService, CommandHandlerService>()
.AddSingleton<IClipboardService, ClipboardService>()
.AddCommandHandlers();
}
private static IServiceCollection AddCommandHandlers(this IServiceCollection serviceCollection) public static class Startup
{ {
return serviceCollection public static IServiceCollection AddCoreAppServices(this IServiceCollection serviceCollection)
.AddSingleton<ICommandHandler, NavigationCommandHandler>() {
.AddSingleton<ICommandHandler, ItemManipulationCommandHandler>(); return serviceCollection
} .AddTransient<ITabViewModel, TabViewModel>()
.AddTransient<IContainerViewModel, ContainerViewModel>()
.AddTransient<IElementViewModel, ElementViewModel>()
.AddTransient<IItemNameConverterService, ItemNameConverterService>()
.AddSingleton<ICommandHandlerService, CommandHandlerService>()
.AddSingleton<IClipboardService, ClipboardService>()
.AddCommandHandlers();
}
private static IServiceCollection AddCommandHandlers(this IServiceCollection serviceCollection)
{
return serviceCollection
.AddSingleton<ICommandHandler, NavigationCommandHandler>()
.AddSingleton<ICommandHandler, ItemManipulationCommandHandler>();
} }
} }

View File

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

@@ -2,21 +2,20 @@ using FileTime.App.Core.Services;
using FileTime.Core.Models; using FileTime.Core.Models;
using MvvmGen; using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
[ViewModel(GenerateConstructor = false)]
public partial class ContainerSizeContainerViewModel : ItemViewModel, IContainerSizeContainerViewModel
{ {
[ViewModel(GenerateConstructor = false)] [Property]
public partial class ContainerSizeContainerViewModel : ItemViewModel, IContainerSizeContainerViewModel private long _size;
public ContainerSizeContainerViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState)
{ {
[Property] }
private long _size;
public ContainerSizeContainerViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState) public void Init(IContainer item, ITabViewModel parentTab)
{ {
} Init((IItem)item, parentTab);
public void Init(IContainer item, ITabViewModel parentTab)
{
Init((IItem)item, parentTab);
}
} }
} }

View File

@@ -2,20 +2,19 @@ using FileTime.App.Core.Services;
using FileTime.Core.Models; using FileTime.Core.Models;
using MvvmGen; using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
[ViewModel(GenerateConstructor = false)]
public partial class ContainerViewModel : ItemViewModel, IContainerViewModel
{ {
[ViewModel(GenerateConstructor = false)] public IContainer? Container => BaseItem as IContainer;
public partial class ContainerViewModel : ItemViewModel, IContainerViewModel
public ContainerViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState)
{ {
public IContainer? Container => BaseItem as IContainer; }
public ContainerViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState) public void Init(IContainer item, ITabViewModel parentTab)
{ {
} Init((IItem)item, parentTab);
public void Init(IContainer item, ITabViewModel parentTab)
{
Init((IItem)item, parentTab);
}
} }
} }

View File

@@ -2,21 +2,20 @@ using FileTime.App.Core.Services;
using FileTime.Core.Models; using FileTime.Core.Models;
using MvvmGen; using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
[ViewModel(GenerateConstructor = false)]
public partial class ElementViewModel : ItemViewModel, IElementViewModel
{ {
[ViewModel(GenerateConstructor = false)] [Property]
public partial class ElementViewModel : ItemViewModel, IElementViewModel private long? _size;
public ElementViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState)
{ {
[Property] }
private long? _size;
public ElementViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState) public void Init(IElement item, ITabViewModel parentTab)
{ {
} Init((IItem)item, parentTab);
public void Init(IElement item, ITabViewModel parentTab)
{
Init((IItem)item, parentTab);
}
} }
} }

View File

@@ -2,18 +2,17 @@ using FileTime.App.Core.Services;
using FileTime.Core.Models; using FileTime.Core.Models;
using MvvmGen; using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
{
[ViewModel(GenerateConstructor = false)]
public partial class FileViewModel : ElementViewModel, IFileViewModel
{
public FileViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState)
{
}
public void Init(IFileElement item, ITabViewModel parentTab) [ViewModel(GenerateConstructor = false)]
{ public partial class FileViewModel : ElementViewModel, IFileViewModel
Init((IElement)item, parentTab); {
} public FileViewModel(IItemNameConverterService _itemNameConverterService, IAppState _appState) : base(_itemNameConverterService, _appState)
{
}
public void Init(IFileElement item, ITabViewModel parentTab)
{
Init((IElement)item, parentTab);
} }
} }

View File

@@ -7,54 +7,54 @@ using FileTime.Core.Models;
using MoreLinq; using MoreLinq;
using MvvmGen; using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
[ViewModel]
[Inject(typeof(IAppState), "_appState")]
[Inject(typeof(IItemNameConverterService), "_itemNameConverterService")]
public abstract partial class ItemViewModel : IItemViewModel
{ {
[ViewModel] [Property]
[Inject(typeof(IAppState), "_appState")] private IItem? _baseItem;
[Inject(typeof(IItemNameConverterService), "_itemNameConverterService")]
public abstract partial class ItemViewModel : IItemViewModel [Property]
private IObservable<IReadOnlyList<ItemNamePart>>? _displayName;
[Property]
private string? _displayNameText;
[Property]
private IObservable<bool>? _isSelected;
[Property]
private IObservable<bool>? _isMarked;
[Property]
private IObservable<ItemViewMode> _viewMode;
[Property]
private DateTime? _createdAt;
[Property]
private string? _attributes;
[Property]
private IObservable<bool> _isAlternative;
public void Init(IItem item, ITabViewModel parentTab)
{ {
[Property] BaseItem = item;
private IItem? _baseItem; DisplayName = _appState.SearchText.Select(s => _itemNameConverterService.GetDisplayName(item.DisplayName, s));
DisplayNameText = item.DisplayName;
IsMarked = parentTab.MarkedItems.ToCollection().Select(m => m.Any(i => i.Path.Path == item.FullName?.Path));
IsSelected = parentTab.CurrentSelectedItem.Select(EqualsTo);
IsAlternative = parentTab.CurrentItemsCollectionObservable.Select(c => c?.Index().FirstOrDefault(i => EqualsTo(i.Value)).Key % 2 == 0);
ViewMode = Observable.CombineLatest(IsMarked, IsSelected, IsAlternative, GenerateViewMode);
Attributes = item.Attributes;
CreatedAt = item.CreatedAt;
}
[Property] private ItemViewMode GenerateViewMode(bool isMarked, bool isSelected, bool sAlternative)
private IObservable<IReadOnlyList<ItemNamePart>>? _displayName;
[Property]
private string? _displayNameText;
[Property]
private IObservable<bool>? _isSelected;
[Property]
private IObservable<bool>? _isMarked;
[Property]
private IObservable<ItemViewMode> _viewMode;
[Property]
private DateTime? _createdAt;
[Property]
private string? _attributes;
[Property]
private IObservable<bool> _isAlternative;
public void Init(IItem item, ITabViewModel parentTab)
{
BaseItem = item;
DisplayName = _appState.SearchText.Select(s => _itemNameConverterService.GetDisplayName(item.DisplayName, s));
DisplayNameText = item.DisplayName;
IsMarked = parentTab.MarkedItems.ToCollection().Select(m => m.Any(i => i.Path.Path == item.FullName?.Path));
IsSelected = parentTab.CurrentSelectedItem.Select(EqualsTo);
IsAlternative = parentTab.CurrentItemsCollectionObservable.Select(c => c?.Index().FirstOrDefault(i => EqualsTo(i.Value)).Key % 2 == 0);
ViewMode = Observable.CombineLatest(IsMarked, IsSelected, IsAlternative, GenerateViewMode);
Attributes = item.Attributes;
CreatedAt = item.CreatedAt;
}
private ItemViewMode GenerateViewMode(bool isMarked, bool isSelected, bool sAlternative)
=> (isMarked, isSelected, sAlternative) switch => (isMarked, isSelected, sAlternative) switch
{ {
(true, true, _) => ItemViewMode.MarkedSelected, (true, true, _) => ItemViewMode.MarkedSelected,
@@ -65,9 +65,8 @@ namespace FileTime.App.Core.ViewModels
_ => ItemViewMode.Default _ => ItemViewMode.Default
}; };
public bool EqualsTo(IItemViewModel? itemViewModel) public bool EqualsTo(IItemViewModel? itemViewModel)
{ {
return BaseItem?.FullName?.Path is string path && path == itemViewModel?.BaseItem?.FullName?.Path; return BaseItem?.FullName?.Path is string path && path == itemViewModel?.BaseItem?.FullName?.Path;
}
} }
} }

View File

@@ -9,118 +9,118 @@ using FileTime.Tools.Extensions;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using MvvmGen; using MvvmGen;
namespace FileTime.App.Core.ViewModels namespace FileTime.App.Core.ViewModels;
[ViewModel]
public partial class TabViewModel : ITabViewModel, IDisposable
{ {
[ViewModel] private readonly IServiceProvider _serviceProvider;
public partial class TabViewModel : ITabViewModel, IDisposable private readonly IItemNameConverterService _itemNameConverterService;
private readonly IAppState _appState;
private readonly IRxSchedulerService _rxSchedulerService;
private readonly SourceList<IAbsolutePath> _markedItems = new();
private readonly List<IDisposable> _disposables = new();
private bool disposed;
public ITab? Tab { get; private set; }
public int TabNumber { get; private set; }
public IObservable<bool> IsSelected { get; }
public IObservable<IContainer?> CurrentLocation { get; private set; } = null!;
public IObservable<IItemViewModel?> CurrentSelectedItem { get; private set; } = null!;
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> CurrentItems { get; private set; } = null!;
public IObservable<IChangeSet<IAbsolutePath>> MarkedItems { get; }
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> SelectedsChildren { get; private set; } = null!;
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> ParentsChildren { get; private set; } = null!;
public IObservable<IReadOnlyCollection<IItemViewModel>?> CurrentItemsCollectionObservable { get; private set; } = null!;
[Property]
private BindedCollection<IItemViewModel>? _currentItemsCollection;
[Property]
private BindedCollection<IItemViewModel>? _parentsChildrenCollection;
[Property]
private BindedCollection<IItemViewModel>? _selectedsChildrenCollection;
public TabViewModel(
IServiceProvider serviceProvider,
IItemNameConverterService itemNameConverterService,
IAppState appState,
IRxSchedulerService rxSchedulerService)
{ {
private readonly IServiceProvider _serviceProvider; _serviceProvider = serviceProvider;
private readonly IItemNameConverterService _itemNameConverterService; _itemNameConverterService = itemNameConverterService;
private readonly IAppState _appState; _appState = appState;
private readonly IRxSchedulerService _rxSchedulerService;
private readonly SourceList<IAbsolutePath> _markedItems = new();
private readonly List<IDisposable> _disposables = new();
private bool disposed;
public ITab? Tab { get; private set; } MarkedItems = _markedItems.Connect().StartWithEmpty();
public int TabNumber { get; private set; } IsSelected = _appState.SelectedTab.Select(s => s == this);
_rxSchedulerService = rxSchedulerService;
}
public IObservable<bool> IsSelected { get; } public void Init(ITab tab, int tabNumber)
{
Tab = tab;
TabNumber = tabNumber;
public IObservable<IContainer?> CurrentLocation { get; private set; } = null!; CurrentLocation = tab.CurrentLocation.AsObservable();
public IObservable<IItemViewModel?> CurrentSelectedItem { get; private set; } = null!; CurrentItems = tab.CurrentItems
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> CurrentItems { get; private set; } = null!; .Select(items => items?.Transform(MapItemToViewModel))
public IObservable<IChangeSet<IAbsolutePath>> MarkedItems { get; } .ObserveOn(_rxSchedulerService.GetWorkerScheduler())
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> SelectedsChildren { get; private set; } = null!; .SubscribeOn(_rxSchedulerService.GetUIScheduler())
public IObservable<IObservable<IChangeSet<IItemViewModel>>?> ParentsChildren { get; private set; } = null!; .Publish(null)
.RefCount();
public IObservable<IReadOnlyCollection<IItemViewModel>?> CurrentItemsCollectionObservable { get; private set; } = null!; CurrentSelectedItem =
Observable.CombineLatest(
[Property]
private BindedCollection<IItemViewModel>? _currentItemsCollection;
[Property]
private BindedCollection<IItemViewModel>? _parentsChildrenCollection;
[Property]
private BindedCollection<IItemViewModel>? _selectedsChildrenCollection;
public TabViewModel(
IServiceProvider serviceProvider,
IItemNameConverterService itemNameConverterService,
IAppState appState,
IRxSchedulerService rxSchedulerService)
{
_serviceProvider = serviceProvider;
_itemNameConverterService = itemNameConverterService;
_appState = appState;
MarkedItems = _markedItems.Connect().StartWithEmpty();
IsSelected = _appState.SelectedTab.Select(s => s == this);
_rxSchedulerService = rxSchedulerService;
}
public void Init(ITab tab, int tabNumber)
{
Tab = tab;
TabNumber = tabNumber;
CurrentLocation = tab.CurrentLocation.AsObservable();
CurrentItems = tab.CurrentItems
.Select(items => items?.Transform(MapItemToViewModel))
.ObserveOn(_rxSchedulerService.GetWorkerScheduler())
.SubscribeOn(_rxSchedulerService.GetUIScheduler())
.Publish(null)
.RefCount();
CurrentSelectedItem =
Observable.CombineLatest(
CurrentItems, CurrentItems,
tab.CurrentSelectedItem, tab.CurrentSelectedItem,
(currentItems, currentSelectedItemPath) => (currentItems, currentSelectedItemPath) =>
currentItems == null currentItems == null
? Observable.Return((IItemViewModel?)null) ? Observable.Return((IItemViewModel?)null)
: currentItems : currentItems
.ToCollection() .ToCollection()
.Select(items => items.FirstOrDefault(i => i.BaseItem?.FullName == currentSelectedItemPath?.Path)) .Select(items => items.FirstOrDefault(i => i.BaseItem?.FullName == currentSelectedItemPath?.Path))
) )
.Switch() .Switch()
.Publish(null) .Publish(null)
.RefCount(); .RefCount();
SelectedsChildren = InitSelectedsChildren(); SelectedsChildren = InitSelectedsChildren();
ParentsChildren = InitParentsChildren(); ParentsChildren = InitParentsChildren();
CurrentItemsCollectionObservable = CurrentItems CurrentItemsCollectionObservable = CurrentItems
.Select(c => c != null ? c.ToCollection() : Observable.Return((IReadOnlyCollection<IItemViewModel>?)null)) .Select(c => c != null ? c.ToCollection() : Observable.Return((IReadOnlyCollection<IItemViewModel>?)null))
.Switch() .Switch()
.Publish(null) .Publish(null)
.RefCount(); .RefCount();
CurrentItems.Subscribe(children => CurrentItems.Subscribe(children =>
{ {
CurrentItemsCollection?.Dispose(); CurrentItemsCollection?.Dispose();
CurrentItemsCollection = children.MapNull(c => new BindedCollection<IItemViewModel>(c!)); CurrentItemsCollection = children.MapNull(c => new BindedCollection<IItemViewModel>(c!));
}); });
ParentsChildren.Subscribe(children => ParentsChildren.Subscribe(children =>
{ {
ParentsChildrenCollection?.Dispose(); ParentsChildrenCollection?.Dispose();
ParentsChildrenCollection = children.MapNull(c => new BindedCollection<IItemViewModel>(c!)); ParentsChildrenCollection = children.MapNull(c => new BindedCollection<IItemViewModel>(c!));
}); });
SelectedsChildren.Subscribe(children => SelectedsChildren.Subscribe(children =>
{ {
SelectedsChildrenCollection?.Dispose(); SelectedsChildrenCollection?.Dispose();
SelectedsChildrenCollection = children.MapNull(c => new BindedCollection<IItemViewModel>(c!)); SelectedsChildrenCollection = children.MapNull(c => new BindedCollection<IItemViewModel>(c!));
}); });
tab.CurrentLocation.Subscribe((_) => _markedItems.Clear()); tab.CurrentLocation.Subscribe((_) => _markedItems.Clear());
IObservable<IObservable<IChangeSet<IItemViewModel>>?> InitSelectedsChildren() IObservable<IObservable<IChangeSet<IItemViewModel>>?> InitSelectedsChildren()
{ {
var currentSelectedItemThrottled = CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250)).Publish(null).RefCount(); var currentSelectedItemThrottled = CurrentSelectedItem.Throttle(TimeSpan.FromMilliseconds(250)).Publish(null).RefCount();
return Observable.Merge( return Observable.Merge(
currentSelectedItemThrottled currentSelectedItemThrottled
.WhereNotNull() .WhereNotNull()
.OfType<IContainerViewModel>() .OfType<IContainerViewModel>()
@@ -136,17 +136,17 @@ namespace FileTime.App.Core.ViewModels
.SubscribeOn(_rxSchedulerService.GetUIScheduler()) .SubscribeOn(_rxSchedulerService.GetUIScheduler())
.Publish(null) .Publish(null)
.RefCount(); .RefCount();
} }
IObservable<IObservable<IChangeSet<IItemViewModel>>?> InitParentsChildren() IObservable<IObservable<IChangeSet<IItemViewModel>>?> InitParentsChildren()
{ {
var parentThrottled = CurrentLocation var parentThrottled = CurrentLocation
.Select(l => l?.Parent) .Select(l => l?.Parent)
.DistinctUntilChanged() .DistinctUntilChanged()
.Publish(null) .Publish(null)
.RefCount(); .RefCount();
return Observable.Merge( return Observable.Merge(
parentThrottled parentThrottled
.Where(p => p is not null) .Where(p => p is not null)
.Select(p => Observable.FromAsync(async () => (IContainer)await p!.ResolveAsync())) .Select(p => Observable.FromAsync(async () => (IContainer)await p!.ResolveAsync()))
@@ -162,85 +162,84 @@ namespace FileTime.App.Core.ViewModels
.SubscribeOn(_rxSchedulerService.GetUIScheduler()) .SubscribeOn(_rxSchedulerService.GetUIScheduler())
.Publish(null) .Publish(null)
.RefCount(); .RefCount();
}
}
private static async Task<IItem> MapItem(IAbsolutePath item)
=> await item.ResolveAsync(forceResolve: true, itemInitializationSettings: new ItemInitializationSettings(true));
private IItemViewModel MapItemToViewModel(IItem item)
{
if (item is IContainer container)
{
var containerViewModel = _serviceProvider.GetInitableResolver<IContainer, ITabViewModel>(container, this).GetRequiredService<IContainerViewModel>();
return containerViewModel;
}
else if (item is IFileElement fileElement)
{
var fileViewModel = _serviceProvider.GetInitableResolver<IFileElement, ITabViewModel>(fileElement, this).GetRequiredService<IFileViewModel>();
fileViewModel.Size = fileElement.Size;
return fileViewModel;
}
else if (item is IElement element)
{
var elementViewModel = _serviceProvider.GetInitableResolver<IElement, ITabViewModel>(element, this).GetRequiredService<IElementViewModel>();
return elementViewModel;
}
throw new ArgumentException($"{nameof(item)} is not {nameof(IContainer)} neither {nameof(IElement)}");
}
public void AddMarkedItem(IAbsolutePath item) => _markedItems.Add(item);
public void RemoveMarkedItem(IAbsolutePath item)
{
var itemsToRemove = _markedItems.Items.Where(i => i.Path.Path == item.Path.Path).ToList();
_markedItems.RemoveMany(itemsToRemove);
}
public void ToggleMarkedItem(IAbsolutePath item)
{
if (_markedItems.Items.Any(i => i.Path.Path == item.Path.Path))
{
RemoveMarkedItem(item);
}
else
{
AddMarkedItem(item);
}
}
public void ClearMarkedItems()
{
_markedItems.Clear();
}
~TabViewModel() => Dispose(false);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed && disposing)
{
foreach (var disposable in _disposables)
{
try
{
disposable.Dispose();
}
catch { }
}
}
disposed = true;
} }
} }
private static async Task<IItem> MapItem(IAbsolutePath item)
=> await item.ResolveAsync(forceResolve: true, itemInitializationSettings: new ItemInitializationSettings(true));
private IItemViewModel MapItemToViewModel(IItem item)
{
if (item is IContainer container)
{
var containerViewModel = _serviceProvider.GetInitableResolver<IContainer, ITabViewModel>(container, this).GetRequiredService<IContainerViewModel>();
return containerViewModel;
}
else if (item is IFileElement fileElement)
{
var fileViewModel = _serviceProvider.GetInitableResolver<IFileElement, ITabViewModel>(fileElement, this).GetRequiredService<IFileViewModel>();
fileViewModel.Size = fileElement.Size;
return fileViewModel;
}
else if (item is IElement element)
{
var elementViewModel = _serviceProvider.GetInitableResolver<IElement, ITabViewModel>(element, this).GetRequiredService<IElementViewModel>();
return elementViewModel;
}
throw new ArgumentException($"{nameof(item)} is not {nameof(IContainer)} neither {nameof(IElement)}");
}
public void AddMarkedItem(IAbsolutePath item) => _markedItems.Add(item);
public void RemoveMarkedItem(IAbsolutePath item)
{
var itemsToRemove = _markedItems.Items.Where(i => i.Path.Path == item.Path.Path).ToList();
_markedItems.RemoveMany(itemsToRemove);
}
public void ToggleMarkedItem(IAbsolutePath item)
{
if (_markedItems.Items.Any(i => i.Path.Path == item.Path.Path))
{
RemoveMarkedItem(item);
}
else
{
AddMarkedItem(item);
}
}
public void ClearMarkedItems()
{
_markedItems.Clear();
}
~TabViewModel() => Dispose(false);
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!disposed && disposing)
{
foreach (var disposable in _disposables)
{
try
{
disposable.Dispose();
}
catch { }
}
}
disposed = true;
}
} }

View File

@@ -3,18 +3,17 @@ using FileTime.Core.Services;
using FileTime.Providers.Local; using FileTime.Providers.Local;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace FileTime.App.DependencyInjection namespace FileTime.App.DependencyInjection;
{
public static class DependencyInjection
{
public static IServiceCollection RegisterDefaultServices(IServiceCollection? serviceCollection = null)
{
serviceCollection ??= new ServiceCollection();
return serviceCollection public static class DependencyInjection
.AddTransient<ITab, Tab>() {
.AddCoreAppServices() public static IServiceCollection RegisterDefaultServices(IServiceCollection? serviceCollection = null)
.AddLocalServices(); {
} serviceCollection ??= new ServiceCollection();
return serviceCollection
.AddTransient<ITab, Tab>()
.AddCoreAppServices()
.AddLocalServices();
} }
} }

View File

@@ -5,6 +5,11 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
</ItemGroup> </ItemGroup>

View File

@@ -6,4 +6,8 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
</Project> </Project>

View File

@@ -7,6 +7,10 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FileTime.ConsoleUI.App\FileTime.ConsoleUI.App.csproj" /> <ProjectReference Include="..\FileTime.ConsoleUI.App\FileTime.ConsoleUI.App.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -1,7 +1,6 @@
namespace FileTime.Core.Behaviors namespace FileTime.Core.Behaviors;
public interface IOnContainerEnter
{ {
public interface IOnContainerEnter Task OnEnter();
{
Task OnEnter();
}
} }

View File

@@ -1,7 +1,6 @@
namespace FileTime.Core.Command namespace FileTime.Core.Command;
public interface ICommand
{ {
public interface ICommand
{
}
} }

View File

@@ -1,7 +1,6 @@
namespace FileTime.Core.Command namespace FileTime.Core.Command;
public interface ITransportationCommand : ICommand
{ {
public interface ITransportationCommand : ICommand
{
}
} }

View File

@@ -1,9 +1,8 @@
namespace FileTime.Core.Enums namespace FileTime.Core.Enums;
public enum AbsolutePathType
{ {
public enum AbsolutePathType Unknown,
{ Container,
Unknown, Element
Container,
Element
}
} }

View File

@@ -1,9 +1,8 @@
namespace FileTime.Core.Enums namespace FileTime.Core.Enums;
public enum SupportsDelete
{ {
public enum SupportsDelete False,
{ True,
False, HardDeleteOnly,
True,
HardDeleteOnly,
}
} }

View File

@@ -6,6 +6,10 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DynamicData" Version="7.6.7" /> <PackageReference Include="DynamicData" Version="7.6.7" />
<PackageReference Include="System.Reactive" Version="5.0.0" /> <PackageReference Include="System.Reactive" Version="5.0.0" />

View File

@@ -1,7 +1,6 @@
namespace FileTime.Core.Models namespace FileTime.Core.Models;
public static class Constants
{ {
public static class Constants public const char SeparatorChar = '/';
{
public const char SeparatorChar = '/';
}
} }

View File

@@ -1,17 +1,16 @@
namespace FileTime.Core.Models namespace FileTime.Core.Models;
{
public record FullName(string Path)
{
public FullName? GetParent()
{
if (Path is null) return null;
var pathParts = Path.TrimEnd(Constants.SeparatorChar).Split(Constants.SeparatorChar); public record FullName(string Path)
return pathParts.Length switch {
{ public FullName? GetParent()
> 1 => new(string.Join(Constants.SeparatorChar, pathParts.SkipLast(1))), {
_ => null if (Path is null) return null;
};
} var pathParts = Path.TrimEnd(Constants.SeparatorChar).Split(Constants.SeparatorChar);
return pathParts.Length switch
{
> 1 => new(string.Join(Constants.SeparatorChar, pathParts.SkipLast(1))),
_ => null
};
} }
} }

View File

@@ -1,16 +1,15 @@
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Services; using FileTime.Core.Services;
namespace FileTime.Core.Models namespace FileTime.Core.Models;
{
public interface IAbsolutePath
{
IContentProvider ContentProvider { get; }
IContentProvider? VirtualContentProvider { get; }
FullName Path { get; }
AbsolutePathType Type { get; }
Task<IItem> ResolveAsync(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default); public interface IAbsolutePath
Task<IItem?> ResolveAsyncSafe(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default); {
} IContentProvider ContentProvider { get; }
IContentProvider? VirtualContentProvider { get; }
FullName Path { get; }
AbsolutePathType Type { get; }
Task<IItem> ResolveAsync(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default);
Task<IItem?> ResolveAsyncSafe(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default);
} }

View File

@@ -1,10 +1,9 @@
using DynamicData; using DynamicData;
namespace FileTime.Core.Models namespace FileTime.Core.Models;
public interface IContainer : IItem
{ {
public interface IContainer : IItem IObservable<IObservable<IChangeSet<IAbsolutePath>>?> Items { get; }
{ IObservable<bool> IsLoading { get; }
IObservable<IObservable<IChangeSet<IAbsolutePath>>?> Items { get; }
IObservable<bool> IsLoading { get; }
}
} }

View File

@@ -1,7 +1,6 @@
namespace FileTime.Core.Models namespace FileTime.Core.Models;
public interface IElement : IItem
{ {
public interface IElement : IItem
{
}
} }

View File

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

View File

@@ -1,23 +1,22 @@
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Services; using FileTime.Core.Services;
namespace FileTime.Core.Models namespace FileTime.Core.Models;
public interface IItem
{ {
public interface IItem string Name { get; }
{ string DisplayName { get; }
string Name { get; } FullName? FullName { get; }
string DisplayName { get; } NativePath? NativePath { get; }
FullName? FullName { get; } IAbsolutePath? Parent { get; }
NativePath? NativePath { get; } bool IsHidden { get; }
IAbsolutePath? Parent { get; } bool IsExists { get; }
bool IsHidden { get; } DateTime? CreatedAt { get; }
bool IsExists { get; } SupportsDelete CanDelete { get; }
DateTime? CreatedAt { get; } bool CanRename { get; }
SupportsDelete CanDelete { get; } IContentProvider Provider { get; }
bool CanRename { get; } string? Attributes { get; }
IContentProvider Provider { get; } AbsolutePathType Type { get; }
string? Attributes { get; } IObservable<IEnumerable<Exception>> Exceptions { get; }
AbsolutePathType Type { get; }
IObservable<IEnumerable<Exception>> Exceptions { get; }
}
} }

View File

@@ -1,12 +1,11 @@
namespace FileTime.Core.Models namespace FileTime.Core.Models;
{
public readonly struct ItemInitializationSettings
{
public readonly bool SkipChildInitialization;
public ItemInitializationSettings(bool skipChildInitialization) public readonly struct ItemInitializationSettings
{ {
SkipChildInitialization = skipChildInitialization; public readonly bool SkipChildInitialization;
}
public ItemInitializationSettings(bool skipChildInitialization)
{
SkipChildInitialization = skipChildInitialization;
} }
} }

View File

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

View File

@@ -1,4 +1,3 @@
namespace FileTime.Core.Models namespace FileTime.Core.Models;
{
public record NativePath(string Path); public record NativePath(string Path);
}

View File

@@ -2,22 +2,21 @@ using FileTime.Core.Behaviors;
using FileTime.Core.Enums; 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( bool forceResolve = false,
FullName fullName, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default);
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default);
Task<IItem> GetItemByNativePathAsync( Task<IItem> GetItemByNativePathAsync(
NativePath nativePath, NativePath nativePath,
bool forceResolve = false, bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown, AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default); ItemInitializationSettings itemInitializationSettings = default);
Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName); Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName);
}
} }

View File

@@ -2,18 +2,17 @@ using DynamicData;
using FileTime.Core.Models; using FileTime.Core.Models;
using InitableService; using InitableService;
namespace FileTime.Core.Services namespace FileTime.Core.Services;
{
public interface ITab : IInitable<IContainer>
{
IObservable<IContainer?> CurrentLocation { get; }
IObservable<IAbsolutePath?> CurrentSelectedItem { get; }
IObservable<IObservable<IChangeSet<IItem>>?> CurrentItems { get; }
void SetCurrentLocation(IContainer newLocation); public interface ITab : IInitable<IContainer>
void AddSelectedItemsTransformator(ItemsTransformator transformator); {
void RemoveSelectedItemsTransformator(ItemsTransformator transformator); IObservable<IContainer?> CurrentLocation { get; }
void RemoveSelectedItemsTransformatorByName(string name); IObservable<IAbsolutePath?> CurrentSelectedItem { get; }
void SetSelectedItem(IAbsolutePath newSelectedItem); IObservable<IObservable<IChangeSet<IItem>>?> CurrentItems { get; }
}
void SetCurrentLocation(IContainer newLocation);
void AddSelectedItemsTransformator(ItemsTransformator transformator);
void RemoveSelectedItemsTransformator(ItemsTransformator transformator);
void RemoveSelectedItemsTransformatorByName(string name);
void SetSelectedItem(IAbsolutePath newSelectedItem);
} }

View File

@@ -1,7 +1,6 @@
namespace FileTime.Core.Command.Copy namespace FileTime.Core.Command.Copy;
{
public class CopyCommand : ITransportationCommand public class CopyCommand : ITransportationCommand
{ {
}
} }

View File

@@ -1,9 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" /> <ProjectReference Include="..\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
</ItemGroup> </ItemGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>

View File

@@ -1,9 +1,8 @@
namespace FileTime.Core.Command namespace FileTime.Core.Command;
public enum TransportMode
{ {
public enum TransportMode Merge,
{ Overwrite,
Merge, Skip
Overwrite,
Skip
}
} }

View File

@@ -1,45 +1,44 @@
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Services; using FileTime.Core.Services;
namespace FileTime.Core.Models namespace FileTime.Core.Models;
public class AbsolutePath : IAbsolutePath
{ {
public class AbsolutePath : IAbsolutePath public IContentProvider ContentProvider { get; }
public IContentProvider? VirtualContentProvider { get; }
public FullName Path { get; }
public AbsolutePathType Type { get; }
public AbsolutePath(IContentProvider contentProvider, FullName path, AbsolutePathType type, IContentProvider? virtualContentProvider = null)
{ {
public IContentProvider ContentProvider { get; } ContentProvider = contentProvider;
public IContentProvider? VirtualContentProvider { get; } Path = path;
VirtualContentProvider = virtualContentProvider;
Type = type;
}
public FullName Path { get; } public AbsolutePath(IItem item, IContentProvider? virtualContentProvider = null)
public AbsolutePathType Type { get; } {
ContentProvider = item.Provider;
Path = item.FullName ?? throw new ArgumentException($"{nameof(item.FullName)} can not be null.", nameof(item));
VirtualContentProvider = virtualContentProvider;
Type = item.Type;
}
public AbsolutePath(IContentProvider contentProvider, FullName path, AbsolutePathType type, IContentProvider? virtualContentProvider = null) public async Task<IItem> ResolveAsync(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default)
{
var provider = VirtualContentProvider ?? ContentProvider;
return await provider.GetItemByFullNameAsync(Path, forceResolve, Type, itemInitializationSettings);
}
public async Task<IItem?> ResolveAsyncSafe(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default)
{
try
{ {
ContentProvider = contentProvider; return await ResolveAsync(forceResolve, itemInitializationSettings);
Path = path;
VirtualContentProvider = virtualContentProvider;
Type = type;
}
public AbsolutePath(IItem item, IContentProvider? virtualContentProvider = null)
{
ContentProvider = item.Provider;
Path = item.FullName ?? throw new ArgumentException($"{nameof(item.FullName)} can not be null.", nameof(item));
VirtualContentProvider = virtualContentProvider;
Type = item.Type;
}
public async Task<IItem> ResolveAsync(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default)
{
var provider = VirtualContentProvider ?? ContentProvider;
return await provider.GetItemByFullNameAsync(Path, forceResolve, Type, itemInitializationSettings);
}
public async Task<IItem?> ResolveAsyncSafe(bool forceResolve = false, ItemInitializationSettings itemInitializationSettings = default)
{
try
{
return await ResolveAsync(forceResolve, itemInitializationSettings);
}
catch { return null; }
} }
catch { return null; }
} }
} }

View File

@@ -4,26 +4,25 @@ using DynamicData;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Services; using FileTime.Core.Services;
namespace FileTime.Core.Models namespace FileTime.Core.Models;
public record Container(
string Name,
string DisplayName,
FullName FullName,
NativePath NativePath,
IAbsolutePath? Parent,
bool IsHidden,
bool IsExists,
DateTime? CreatedAt,
SupportsDelete CanDelete,
bool CanRename,
string? Attributes,
IContentProvider Provider,
IObservable<IEnumerable<Exception>> Exceptions,
IObservable<IObservable<IChangeSet<IAbsolutePath>>?> Items) : IContainer
{ {
public record Container( BehaviorSubject<bool> IsLoading { get; } = new BehaviorSubject<bool>(false);
string Name, IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable();
string DisplayName, public AbsolutePathType Type => AbsolutePathType.Container;
FullName FullName,
NativePath NativePath,
IAbsolutePath? Parent,
bool IsHidden,
bool IsExists,
DateTime? CreatedAt,
SupportsDelete CanDelete,
bool CanRename,
string? Attributes,
IContentProvider Provider,
IObservable<IEnumerable<Exception>> Exceptions,
IObservable<IObservable<IChangeSet<IAbsolutePath>>?> Items) : IContainer
{
BehaviorSubject<bool> IsLoading { get; } = new BehaviorSubject<bool>(false);
IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable();
public AbsolutePathType Type => AbsolutePathType.Container;
}
} }

View File

@@ -1,23 +1,22 @@
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Services; using FileTime.Core.Services;
namespace FileTime.Core.Models namespace FileTime.Core.Models;
public record Element(
string Name,
string DisplayName,
FullName FullName,
NativePath NativePath,
IAbsolutePath? Parent,
bool IsHidden,
bool IsExists,
DateTime? CreatedAt,
SupportsDelete CanDelete,
bool CanRename,
string? Attributes,
IContentProvider Provider,
IObservable<IEnumerable<Exception>> Exceptions) : IElement
{ {
public record Element( public AbsolutePathType Type => AbsolutePathType.Element;
string Name,
string DisplayName,
FullName FullName,
NativePath NativePath,
IAbsolutePath? Parent,
bool IsHidden,
bool IsExists,
DateTime? CreatedAt,
SupportsDelete CanDelete,
bool CanRename,
string? Attributes,
IContentProvider Provider,
IObservable<IEnumerable<Exception>> Exceptions) : IElement
{
public AbsolutePathType Type => AbsolutePathType.Element;
}
} }

View File

@@ -1,9 +1,9 @@
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Services; using FileTime.Core.Services;
namespace FileTime.Core.Models namespace FileTime.Core.Models;
{
public record FileElement( public record FileElement(
string Name, string Name,
string DisplayName, string DisplayName,
FullName FullName, FullName FullName,
@@ -32,5 +32,4 @@ namespace FileTime.Core.Models
Attributes, Attributes,
Provider, Provider,
Exceptions Exceptions
), IFileElement; ), IFileElement;
}

View File

@@ -6,6 +6,10 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" /> <ProjectReference Include="..\FileTime.Core.Abstraction\FileTime.Core.Abstraction.csproj" />
</ItemGroup> </ItemGroup>

View File

@@ -4,64 +4,63 @@ using DynamicData;
using FileTime.Core.Enums; using FileTime.Core.Enums;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.Core.Services namespace FileTime.Core.Services;
public abstract class ContentProviderBase : IContentProvider
{ {
public abstract class ContentProviderBase : IContentProvider protected BehaviorSubject<IObservable<IChangeSet<IAbsolutePath>>?> Items { get; } = new (null);
IObservable<IObservable<IChangeSet<IAbsolutePath>>?> IContainer.Items => Items;
public string Name { get; }
public string DisplayName { get; }
public FullName? FullName => null;
public NativePath? NativePath => null;
public bool IsHidden => false;
public bool IsExists => true;
public SupportsDelete CanDelete => SupportsDelete.False;
public bool CanRename => false;
public IContentProvider Provider => this;
public IAbsolutePath? Parent => null;
public DateTime? CreatedAt => null;
public string? Attributes => null;
protected BehaviorSubject<bool> IsLoading { get; } = new(false);
IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable();
public AbsolutePathType Type => AbsolutePathType.Container;
public IObservable<IEnumerable<Exception>> Exceptions => Observable.Return(Enumerable.Empty<Exception>());
protected ContentProviderBase(string name)
{ {
protected BehaviorSubject<IObservable<IChangeSet<IAbsolutePath>>?> Items { get; } = new (null); DisplayName = Name = name;
IObservable<IObservable<IChangeSet<IAbsolutePath>>?> IContainer.Items => Items;
public string Name { get; }
public string DisplayName { get; }
public FullName? FullName => null;
public NativePath? NativePath => null;
public bool IsHidden => false;
public bool IsExists => true;
public SupportsDelete CanDelete => SupportsDelete.False;
public bool CanRename => false;
public IContentProvider Provider => this;
public IAbsolutePath? Parent => null;
public DateTime? CreatedAt => null;
public string? Attributes => null;
protected BehaviorSubject<bool> IsLoading { get; } = new(false);
IObservable<bool> IContainer.IsLoading => IsLoading.AsObservable();
public AbsolutePathType Type => AbsolutePathType.Container;
public IObservable<IEnumerable<Exception>> Exceptions => Observable.Return(Enumerable.Empty<Exception>());
protected ContentProviderBase(string name)
{
DisplayName = Name = name;
}
public virtual Task OnEnter() => Task.CompletedTask;
public virtual async Task<IItem> GetItemByFullNameAsync(
FullName fullName,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default)
=> await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType, itemInitializationSettings);
public abstract Task<IItem> GetItemByNativePathAsync(
NativePath nativePath,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default);
public abstract Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName);
public abstract NativePath GetNativePath(FullName fullName);
} }
public virtual Task OnEnter() => Task.CompletedTask;
public virtual async Task<IItem> GetItemByFullNameAsync(
FullName fullName,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default)
=> await GetItemByNativePathAsync(GetNativePath(fullName), forceResolve, forceResolvePathType, itemInitializationSettings);
public abstract Task<IItem> GetItemByNativePathAsync(
NativePath nativePath,
bool forceResolve = false,
AbsolutePathType forceResolvePathType = AbsolutePathType.Unknown,
ItemInitializationSettings itemInitializationSettings = default);
public abstract Task<List<IAbsolutePath>> GetItemsByContainerAsync(FullName fullName);
public abstract NativePath GetNativePath(FullName fullName);
} }

View File

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

View File

@@ -3,24 +3,24 @@ using System.Reactive.Subjects;
using DynamicData; using DynamicData;
using FileTime.Core.Models; using FileTime.Core.Models;
namespace FileTime.Core.Services namespace FileTime.Core.Services;
public class Tab : ITab
{ {
public class Tab : ITab private readonly BehaviorSubject<IContainer?> _currentLocation = 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<IObservable<IChangeSet<IItem>>?> CurrentItems { get; }
public IObservable<IAbsolutePath?> CurrentSelectedItem { get; }
public Tab()
{ {
private readonly BehaviorSubject<IContainer?> _currentLocation = new(null); CurrentLocation = _currentLocation.DistinctUntilChanged().Publish(null).RefCount();
private readonly BehaviorSubject<IAbsolutePath?> _currentSelectedItem = new(null); CurrentItems =
private readonly List<ItemsTransformator> _transformators = new(); Observable.Merge(
private IAbsolutePath? _currentSelectedItemCached;
public IObservable<IContainer?> CurrentLocation { get; }
public IObservable<IObservable<IChangeSet<IItem>>?> CurrentItems { get; }
public IObservable<IAbsolutePath?> CurrentSelectedItem { get; }
public Tab()
{
CurrentLocation = _currentLocation.DistinctUntilChanged().Publish(null).RefCount();
CurrentItems =
Observable.Merge(
CurrentLocation CurrentLocation
.Where(c => c is not null) .Where(c => c is not null)
.Select(c => c!.Items) .Select(c => c!.Items)
@@ -33,13 +33,13 @@ namespace FileTime.Core.Services
.Publish((IObservable<IChangeSet<IItem>>?)null) .Publish((IObservable<IChangeSet<IItem>>?)null)
.RefCount(); .RefCount();
CurrentSelectedItem = CurrentSelectedItem =
Observable.CombineLatest( Observable.CombineLatest(
CurrentItems CurrentItems
.Select(c => .Select(c =>
c == null c == null
? Observable.Return<IReadOnlyCollection<IItem>?>(null) ? Observable.Return<IReadOnlyCollection<IItem>?>(null)
: c.ToCollection() : c.ToCollection()
) )
.Switch(), .Switch(),
_currentSelectedItem, _currentSelectedItem,
@@ -55,41 +55,40 @@ namespace FileTime.Core.Services
.Publish(null) .Publish(null)
.RefCount(); .RefCount();
CurrentSelectedItem.Subscribe(s => CurrentSelectedItem.Subscribe(s =>
{
_currentSelectedItemCached = s;
_currentSelectedItem.OnNext(s);
});
}
private async Task<IItem> MapItem(IAbsolutePath item) => await item.ResolveAsync(true);
public void Init(IContainer currentLocation)
{ {
_currentLocation.OnNext(currentLocation); _currentSelectedItemCached = s;
} _currentSelectedItem.OnNext(s);
});
}
private static IAbsolutePath? GetSelectedItemByItems(IEnumerable<IItem> items) private async Task<IItem> MapItem(IAbsolutePath item) => await item.ResolveAsync(true);
{
//TODO:
return new AbsolutePath(items.First());
}
public void SetCurrentLocation(IContainer newLocation) => _currentLocation.OnNext(newLocation); public void Init(IContainer currentLocation)
{
_currentLocation.OnNext(currentLocation);
}
public void SetSelectedItem(IAbsolutePath newSelectedItem) => _currentSelectedItem.OnNext(newSelectedItem); private static IAbsolutePath? GetSelectedItemByItems(IEnumerable<IItem> items)
{
//TODO:
return new AbsolutePath(items.First());
}
public void AddSelectedItemsTransformator(ItemsTransformator transformator) => _transformators.Add(transformator); public void SetCurrentLocation(IContainer newLocation) => _currentLocation.OnNext(newLocation);
public void RemoveSelectedItemsTransformator(ItemsTransformator transformator) => _transformators.Remove(transformator);
public void RemoveSelectedItemsTransformatorByName(string name) => _transformators.RemoveAll(t => t.Name == name);
public async Task OpenSelected() public void SetSelectedItem(IAbsolutePath newSelectedItem) => _currentSelectedItem.OnNext(newSelectedItem);
{
if (_currentSelectedItemCached == null) return;
var resolvedSelectedItem = await _currentSelectedItemCached.ContentProvider.GetItemByFullNameAsync(_currentSelectedItemCached.Path);
if (resolvedSelectedItem is not IContainer resolvedContainer) return; public void AddSelectedItemsTransformator(ItemsTransformator transformator) => _transformators.Add(transformator);
SetCurrentLocation(resolvedContainer); public void RemoveSelectedItemsTransformator(ItemsTransformator transformator) => _transformators.Remove(transformator);
} public void RemoveSelectedItemsTransformatorByName(string name) => _transformators.RemoveAll(t => t.Name == name);
public async Task OpenSelected()
{
if (_currentSelectedItemCached == null) return;
var resolvedSelectedItem = await _currentSelectedItemCached.ContentProvider.GetItemByFullNameAsync(_currentSelectedItemCached.Path);
if (resolvedSelectedItem is not IContainer resolvedContainer) return;
SetCurrentLocation(resolvedContainer);
} }
} }

View File

@@ -1,89 +1,88 @@
using Avalonia.Input; using Avalonia.Input;
using FileTime.App.Core.Command; using FileTime.App.Core.Command;
namespace FileTime.GuiApp.Configuration namespace FileTime.GuiApp.Configuration;
public class CommandBindingConfiguration
{ {
public class CommandBindingConfiguration public List<KeyConfig> Keys { get; set; } = new List<KeyConfig>();
public Commands Command { get; set; } = Commands.None;
public string KeysDisplayText => GetKeysDisplayText();
public CommandBindingConfiguration() { }
public CommandBindingConfiguration(Commands command, IEnumerable<KeyConfig> keys)
{ {
public List<KeyConfig> Keys { get; set; } = new List<KeyConfig>(); Keys = new List<KeyConfig>(keys);
Command = command;
}
public Commands Command { get; set; } = Commands.None; public CommandBindingConfiguration(Commands command, KeyConfig key)
{
Keys = new List<KeyConfig>() { key };
Command = command;
}
public string KeysDisplayText => GetKeysDisplayText(); public CommandBindingConfiguration(Commands command, IEnumerable<Key> keys)
{
Keys = keys.Select(k => new KeyConfig(k)).ToList();
Command = command;
}
public CommandBindingConfiguration() { } public CommandBindingConfiguration(Commands command, Key key)
{
Keys = new List<KeyConfig>() { new KeyConfig(key) };
Command = command;
}
public CommandBindingConfiguration(Commands command, IEnumerable<KeyConfig> keys) public string GetKeysDisplayText()
{
var s = "";
foreach (var k in Keys)
{ {
Keys = new List<KeyConfig>(keys); var keyString = k.Key.ToString();
Command = command;
}
public CommandBindingConfiguration(Commands command, KeyConfig key) if (keyString.Length == 1)
{
Keys = new List<KeyConfig>() { key };
Command = command;
}
public CommandBindingConfiguration(Commands command, IEnumerable<Key> keys)
{
Keys = keys.Select(k => new KeyConfig(k)).ToList();
Command = command;
}
public CommandBindingConfiguration(Commands command, Key key)
{
Keys = new List<KeyConfig>() { new KeyConfig(key) };
Command = command;
}
public string GetKeysDisplayText()
{
var s = "";
foreach (var k in Keys)
{ {
var keyString = k.Key.ToString(); s += AddKeyWithCtrlOrAlt(k, s, (_, _, _) => k.Shift ? keyString.ToUpper() : keyString.ToLower());
}
if (keyString.Length == 1) else
{ {
s += AddKeyWithCtrlOrAlt(k, s, (_, _, _) => k.Shift ? keyString.ToUpper() : keyString.ToLower()); s += AddKeyWithCtrlOrAlt(k, s, AddSpecialKey);
}
else
{
s += AddKeyWithCtrlOrAlt(k, s, AddSpecialKey);
}
} }
return s;
} }
private static string AddKeyWithCtrlOrAlt(KeyConfig key, string currentText, Func<KeyConfig, string, bool, string> keyProcessor) return s;
{ }
var s = "";
bool ctrlOrAlt = key.Ctrl || key.Alt; private static string AddKeyWithCtrlOrAlt(KeyConfig key, string currentText, Func<KeyConfig, string, bool, string> keyProcessor)
{
var s = "";
if (ctrlOrAlt && currentText.Length > 0 && currentText.Last() != ' ') s += " "; bool ctrlOrAlt = key.Ctrl || key.Alt;
if (key.Ctrl) s += "CTRL+"; if (ctrlOrAlt && currentText.Length > 0 && currentText.Last() != ' ') s += " ";
if (key.Alt) s += "ALT+";
s += keyProcessor(key, currentText, ctrlOrAlt);
if (ctrlOrAlt) s += " "; if (key.Ctrl) s += "CTRL+";
if (key.Alt) s += "ALT+";
s += keyProcessor(key, currentText, ctrlOrAlt);
return s; if (ctrlOrAlt) s += " ";
}
private static string AddSpecialKey(KeyConfig key, string currentText, bool wasCtrlOrAlt) return s;
{ }
var s = "";
if (currentText.Length > 0 && currentText.Last() != ' ' && !wasCtrlOrAlt) s += " "; private static string AddSpecialKey(KeyConfig key, string currentText, bool wasCtrlOrAlt)
s += key.Key.ToString(); {
if (!wasCtrlOrAlt) s += " "; var s = "";
return s; if (currentText.Length > 0 && currentText.Last() != ' ' && !wasCtrlOrAlt) s += " ";
} s += key.Key.ToString();
if (!wasCtrlOrAlt) s += " ";
return s;
} }
} }

View File

@@ -1,9 +1,8 @@
namespace FileTime.GuiApp.Configuration namespace FileTime.GuiApp.Configuration;
public class KeyBindingConfiguration
{ {
public class KeyBindingConfiguration public bool UseDefaultBindings { get; set; } = true;
{ public List<CommandBindingConfiguration> DefaultKeyBindings { get; set; } = new();
public bool UseDefaultBindings { get; set; } = true; public List<CommandBindingConfiguration> KeyBindings { get; set; } = new();
public List<CommandBindingConfiguration> DefaultKeyBindings { get; set; } = new();
public List<CommandBindingConfiguration> KeyBindings { get; set; } = new();
}
} }

View File

@@ -1,32 +1,31 @@
using Avalonia.Input; using Avalonia.Input;
namespace FileTime.GuiApp.Configuration namespace FileTime.GuiApp.Configuration;
public class KeyConfig
{ {
public class KeyConfig public Key Key { get; set; }
public bool Shift { get; set; }
public bool Alt { get; set; }
public bool Ctrl { get; set; }
public KeyConfig() { }
public KeyConfig(
Key key,
bool shift = false,
bool alt = false,
bool ctrl = false)
{ {
public Key Key { get; set; } Key = key;
public bool Shift { get; set; } Shift = shift;
public bool Alt { get; set; } Alt = alt;
public bool Ctrl { get; set; } Ctrl = ctrl;
public KeyConfig() { }
public KeyConfig(
Key key,
bool shift = false,
bool alt = false,
bool ctrl = false)
{
Key = key;
Shift = shift;
Alt = alt;
Ctrl = ctrl;
}
public bool AreEquals(KeyConfig otherKeyConfig) =>
Key == otherKeyConfig.Key
&& Alt == otherKeyConfig.Alt
&& Shift == otherKeyConfig.Shift
&& Ctrl == otherKeyConfig.Ctrl;
} }
public bool AreEquals(KeyConfig otherKeyConfig) =>
Key == otherKeyConfig.Key
&& Alt == otherKeyConfig.Alt
&& Shift == otherKeyConfig.Shift
&& Ctrl == otherKeyConfig.Ctrl;
} }

View File

@@ -3,125 +3,124 @@ using FileTime.App.Core.Command;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace FileTime.GuiApp.Configuration namespace FileTime.GuiApp.Configuration;
public static class MainConfiguration
{ {
public static class MainConfiguration private static readonly Lazy<List<CommandBindingConfiguration>> _defaultKeybindings = new(InitDefaultKeyBindings);
public static Dictionary<string, string> Configuration { get; }
static MainConfiguration()
{ {
private static readonly Lazy<List<CommandBindingConfiguration>> _defaultKeybindings = new(InitDefaultKeyBindings); Configuration = new();
PopulateDefaultEditorPrograms(Configuration);
PopulateDefaultKeyBindings(Configuration, _defaultKeybindings.Value, SectionNames.KeybindingSectionName + ":" + nameof(KeyBindingConfiguration.DefaultKeyBindings));
}
public static Dictionary<string, string> Configuration { get; } private static void PopulateDefaultKeyBindings(Dictionary<string, string> configuration, List<CommandBindingConfiguration> commandBindingConfigs, string basePath)
{
static MainConfiguration() for (var i = 0; i < commandBindingConfigs.Count; i++)
{ {
Configuration = new(); var baseKey = basePath + $":[{i}]:";
PopulateDefaultEditorPrograms(Configuration); var commandBindingConfig = commandBindingConfigs[i];
PopulateDefaultKeyBindings(Configuration, _defaultKeybindings.Value, SectionNames.KeybindingSectionName + ":" + nameof(KeyBindingConfiguration.DefaultKeyBindings)); configuration.Add(baseKey + nameof(CommandBindingConfiguration.Command), commandBindingConfig.Command.ToString());
}
private static void PopulateDefaultKeyBindings(Dictionary<string, string> configuration, List<CommandBindingConfiguration> commandBindingConfigs, string basePath) for (var j = 0; j < commandBindingConfig.Keys.Count; j++)
{
for (var i = 0; i < commandBindingConfigs.Count; i++)
{ {
var baseKey = basePath + $":[{i}]:"; var key = commandBindingConfig.Keys[j];
var commandBindingConfig = commandBindingConfigs[i]; var keyBaseKey = baseKey + $"keys:[{j}]:";
configuration.Add(baseKey + nameof(CommandBindingConfiguration.Command), commandBindingConfig.Command.ToString()); configuration.Add(keyBaseKey + nameof(KeyConfig.Key), key.Key.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Shift), key.Shift.ToString());
for (var j = 0; j < commandBindingConfig.Keys.Count; j++) configuration.Add(keyBaseKey + nameof(KeyConfig.Alt), key.Alt.ToString());
{ configuration.Add(keyBaseKey + nameof(KeyConfig.Ctrl), key.Ctrl.ToString());
var key = commandBindingConfig.Keys[j];
var keyBaseKey = baseKey + $"keys:[{j}]:";
configuration.Add(keyBaseKey + nameof(KeyConfig.Key), key.Key.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Shift), key.Shift.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Alt), key.Alt.ToString());
configuration.Add(keyBaseKey + nameof(KeyConfig.Ctrl), key.Ctrl.ToString());
}
} }
} }
}
private static List<CommandBindingConfiguration> InitDefaultKeyBindings() private static List<CommandBindingConfiguration> InitDefaultKeyBindings()
{
return new List<CommandBindingConfiguration>()
{ {
return new List<CommandBindingConfiguration>() new CommandBindingConfiguration(Commands.AutoRefresh, new KeyConfig(Key.R, shift: true)),
{ new CommandBindingConfiguration(Commands.ChangeTimelineMode, new[] { Key.T, Key.M }),
new CommandBindingConfiguration(Commands.AutoRefresh, new KeyConfig(Key.R, shift: true)), new CommandBindingConfiguration(Commands.CloseTab, Key.Q),
new CommandBindingConfiguration(Commands.ChangeTimelineMode, new[] { Key.T, Key.M }), new CommandBindingConfiguration(Commands.Compress, new[] { Key.Y, Key.C }),
new CommandBindingConfiguration(Commands.CloseTab, Key.Q), new CommandBindingConfiguration(Commands.Copy, new[] { Key.Y, Key.Y }),
new CommandBindingConfiguration(Commands.Compress, new[] { Key.Y, Key.C }), new CommandBindingConfiguration(Commands.CopyHash, new[] { Key.C, Key.H }),
new CommandBindingConfiguration(Commands.Copy, new[] { Key.Y, Key.Y }), new CommandBindingConfiguration(Commands.CopyPath, new[] { Key.C, Key.P }),
new CommandBindingConfiguration(Commands.CopyHash, new[] { Key.C, Key.H }), new CommandBindingConfiguration(Commands.CreateContainer, Key.F7),
new CommandBindingConfiguration(Commands.CopyPath, new[] { Key.C, Key.P }), new CommandBindingConfiguration(Commands.CreateContainer, new[] { Key.C, Key.C }),
new CommandBindingConfiguration(Commands.CreateContainer, Key.F7), new CommandBindingConfiguration(Commands.CreateElement, new[] { Key.C, Key.E }),
new CommandBindingConfiguration(Commands.CreateContainer, new[] { Key.C, Key.C }), new CommandBindingConfiguration(Commands.Cut, new[] { Key.D, Key.D }),
new CommandBindingConfiguration(Commands.CreateElement, new[] { Key.C, Key.E }), new CommandBindingConfiguration(Commands.Edit, new KeyConfig(Key.F4)),
new CommandBindingConfiguration(Commands.Cut, new[] { Key.D, Key.D }), new CommandBindingConfiguration(Commands.EnterRapidTravel, new KeyConfig(Key.OemComma, shift: true)),
new CommandBindingConfiguration(Commands.Edit, new KeyConfig(Key.F4)), new CommandBindingConfiguration(Commands.FindByName, new[] { Key.F, Key.N }),
new CommandBindingConfiguration(Commands.EnterRapidTravel, new KeyConfig(Key.OemComma, shift: true)), new CommandBindingConfiguration(Commands.FindByNameRegex, new[] { Key.F, Key.R }),
new CommandBindingConfiguration(Commands.FindByName, new[] { Key.F, Key.N }), new CommandBindingConfiguration(Commands.GoToHome, new[] { Key.G, Key.H }),
new CommandBindingConfiguration(Commands.FindByNameRegex, new[] { Key.F, Key.R }), new CommandBindingConfiguration(Commands.GoToPath, new KeyConfig(Key.L, ctrl: true)),
new CommandBindingConfiguration(Commands.GoToHome, new[] { Key.G, Key.H }), new CommandBindingConfiguration(Commands.GoToPath, new[] { Key.G, Key.P }),
new CommandBindingConfiguration(Commands.GoToPath, new KeyConfig(Key.L, ctrl: true)), new CommandBindingConfiguration(Commands.GoToProvider, new[] { Key.G, Key.T }),
new CommandBindingConfiguration(Commands.GoToPath, new[] { Key.G, Key.P }), new CommandBindingConfiguration(Commands.GoToRoot, new[] { Key.G, Key.R }),
new CommandBindingConfiguration(Commands.GoToProvider, new[] { Key.G, Key.T }), new CommandBindingConfiguration(Commands.HardDelete, new[] { new KeyConfig(Key.D,shift: true), new KeyConfig(Key.D, shift: true) }),
new CommandBindingConfiguration(Commands.GoToRoot, new[] { Key.G, Key.R }), new CommandBindingConfiguration(Commands.Mark, Key.Space),
new CommandBindingConfiguration(Commands.HardDelete, new[] { new KeyConfig(Key.D,shift: true), new KeyConfig(Key.D, shift: true) }), new CommandBindingConfiguration(Commands.MoveToLast, new KeyConfig(Key.G, shift: true)),
new CommandBindingConfiguration(Commands.Mark, Key.Space), new CommandBindingConfiguration(Commands.MoveToFirst, new[] { Key.G, Key.G }),
new CommandBindingConfiguration(Commands.MoveToLast, new KeyConfig(Key.G, shift: true)), new CommandBindingConfiguration(Commands.NextTimelineBlock, Key.L ),
new CommandBindingConfiguration(Commands.MoveToFirst, new[] { Key.G, Key.G }), new CommandBindingConfiguration(Commands.NextTimelineCommand, Key.J ),
new CommandBindingConfiguration(Commands.NextTimelineBlock, Key.L ), new CommandBindingConfiguration(Commands.OpenInFileBrowser, new[] { Key.O, Key.E }),
new CommandBindingConfiguration(Commands.NextTimelineCommand, Key.J ), new CommandBindingConfiguration(Commands.PasteMerge, new[] { Key.P, Key.P }),
new CommandBindingConfiguration(Commands.OpenInFileBrowser, new[] { Key.O, Key.E }), new CommandBindingConfiguration(Commands.PasteOverwrite, new[] { Key.P, Key.O }),
new CommandBindingConfiguration(Commands.PasteMerge, new[] { Key.P, Key.P }), new CommandBindingConfiguration(Commands.PasteSkip, new[] { Key.P, Key.S }),
new CommandBindingConfiguration(Commands.PasteOverwrite, new[] { Key.P, Key.O }), new CommandBindingConfiguration(Commands.PinFavorite, new[] { Key.F, Key.P }),
new CommandBindingConfiguration(Commands.PasteSkip, new[] { Key.P, Key.S }), new CommandBindingConfiguration(Commands.PreviousTimelineBlock, Key.H ),
new CommandBindingConfiguration(Commands.PinFavorite, new[] { Key.F, Key.P }), new CommandBindingConfiguration(Commands.PreviousTimelineCommand, Key.K ),
new CommandBindingConfiguration(Commands.PreviousTimelineBlock, Key.H ), new CommandBindingConfiguration(Commands.Refresh, Key.R),
new CommandBindingConfiguration(Commands.PreviousTimelineCommand, Key.K ), new CommandBindingConfiguration(Commands.Rename, Key.F2),
new CommandBindingConfiguration(Commands.Refresh, Key.R), new CommandBindingConfiguration(Commands.Rename, new[] { Key.C, Key.W }),
new CommandBindingConfiguration(Commands.Rename, Key.F2), new CommandBindingConfiguration(Commands.RunCommand, new KeyConfig(Key.D4, shift: true)),
new CommandBindingConfiguration(Commands.Rename, new[] { Key.C, Key.W }), new CommandBindingConfiguration(Commands.ScanContainerSize, new[] { Key.C, Key.S }),
new CommandBindingConfiguration(Commands.RunCommand, new KeyConfig(Key.D4, shift: true)), new CommandBindingConfiguration(Commands.ShowAllShotcut, Key.F1),
new CommandBindingConfiguration(Commands.ScanContainerSize, new[] { Key.C, Key.S }), new CommandBindingConfiguration(Commands.SoftDelete, new[] { new KeyConfig(Key.D), new KeyConfig(Key.D, shift: true) }),
new CommandBindingConfiguration(Commands.ShowAllShotcut, Key.F1), new CommandBindingConfiguration(Commands.SwitchToLastTab, Key.D9),
new CommandBindingConfiguration(Commands.SoftDelete, new[] { new KeyConfig(Key.D), new KeyConfig(Key.D, shift: true) }), new CommandBindingConfiguration(Commands.SwitchToTab1, Key.D1),
new CommandBindingConfiguration(Commands.SwitchToLastTab, Key.D9), new CommandBindingConfiguration(Commands.SwitchToTab2, Key.D2),
new CommandBindingConfiguration(Commands.SwitchToTab1, Key.D1), new CommandBindingConfiguration(Commands.SwitchToTab3, Key.D3),
new CommandBindingConfiguration(Commands.SwitchToTab2, Key.D2), new CommandBindingConfiguration(Commands.SwitchToTab4, Key.D4),
new CommandBindingConfiguration(Commands.SwitchToTab3, Key.D3), new CommandBindingConfiguration(Commands.SwitchToTab5, Key.D5),
new CommandBindingConfiguration(Commands.SwitchToTab4, Key.D4), new CommandBindingConfiguration(Commands.SwitchToTab6, Key.D6),
new CommandBindingConfiguration(Commands.SwitchToTab5, Key.D5), new CommandBindingConfiguration(Commands.SwitchToTab7, Key.D7),
new CommandBindingConfiguration(Commands.SwitchToTab6, Key.D6), new CommandBindingConfiguration(Commands.SwitchToTab8, Key.D8),
new CommandBindingConfiguration(Commands.SwitchToTab7, Key.D7), new CommandBindingConfiguration(Commands.TimelinePause, new[] { Key.T, Key.P }),
new CommandBindingConfiguration(Commands.SwitchToTab8, Key.D8), new CommandBindingConfiguration(Commands.TimelineRefresh, new[] { Key.T, Key.R }),
new CommandBindingConfiguration(Commands.TimelinePause, new[] { Key.T, Key.P }), new CommandBindingConfiguration(Commands.TimelineStart, new[] { Key.T, Key.S }),
new CommandBindingConfiguration(Commands.TimelineRefresh, new[] { Key.T, Key.R }), new CommandBindingConfiguration(Commands.ToggleAdvancedIcons, new[] { Key.Z, Key.I }),
new CommandBindingConfiguration(Commands.TimelineStart, new[] { Key.T, Key.S }), new CommandBindingConfiguration(Commands.GoUp, Key.Left),
new CommandBindingConfiguration(Commands.ToggleAdvancedIcons, new[] { Key.Z, Key.I }), new CommandBindingConfiguration(Commands.Open, Key.Right),
new CommandBindingConfiguration(Commands.GoUp, Key.Left), new CommandBindingConfiguration(Commands.OpenOrRun, Key.Enter),
new CommandBindingConfiguration(Commands.Open, Key.Right), new CommandBindingConfiguration(Commands.MoveCursorUp, Key.Up),
new CommandBindingConfiguration(Commands.OpenOrRun, Key.Enter), new CommandBindingConfiguration(Commands.MoveCursorDown, Key.Down),
new CommandBindingConfiguration(Commands.MoveCursorUp, Key.Up), new CommandBindingConfiguration(Commands.MoveCursorUpPage, Key.PageUp),
new CommandBindingConfiguration(Commands.MoveCursorDown, Key.Down), new CommandBindingConfiguration(Commands.MoveCursorDownPage, Key.PageDown),
new CommandBindingConfiguration(Commands.MoveCursorUpPage, Key.PageUp), };
new CommandBindingConfiguration(Commands.MoveCursorDownPage, Key.PageDown), }
};
}
private static void PopulateDefaultEditorPrograms(Dictionary<string, string> configuration) private static void PopulateDefaultEditorPrograms(Dictionary<string, string> configuration)
{
var editorPrograms = new List<ProgramConfiguration>()
{ {
var editorPrograms = new List<ProgramConfiguration>() new ProgramConfiguration(@"c:\Program Files\Notepad++\notepad++.exe"),
{ new ProgramConfiguration("notepad.exe"),
new ProgramConfiguration(@"c:\Program Files\Notepad++\notepad++.exe"), };
new ProgramConfiguration("notepad.exe"),
};
for (var i = 0; i < editorPrograms.Count; i++) for (var i = 0; i < editorPrograms.Count; i++)
{ {
if (editorPrograms[i].Path is not string path) continue; if (editorPrograms[i].Path is not string path) continue;
configuration.Add($"{SectionNames.ProgramsSectionName}:{nameof(ProgramsConfiguration.DefaultEditorPrograms)}:[{i}]:{nameof(ProgramConfiguration.Path)}", path); configuration.Add($"{SectionNames.ProgramsSectionName}:{nameof(ProgramsConfiguration.DefaultEditorPrograms)}:[{i}]:{nameof(ProgramConfiguration.Path)}", path);
if (editorPrograms[i].Arguments is string arguments) if (editorPrograms[i].Arguments is string arguments)
{ {
configuration.Add($"{SectionNames.ProgramsSectionName}:{nameof(ProgramsConfiguration.DefaultEditorPrograms)}:[{i}]:{nameof(ProgramConfiguration.Arguments)}", arguments); configuration.Add($"{SectionNames.ProgramsSectionName}:{nameof(ProgramsConfiguration.DefaultEditorPrograms)}:[{i}]:{nameof(ProgramConfiguration.Arguments)}", arguments);
}
} }
} }
} }

View File

@@ -1,16 +1,15 @@
namespace FileTime.GuiApp.Configuration namespace FileTime.GuiApp.Configuration;
public class ProgramConfiguration
{ {
public class ProgramConfiguration public string? Path { get; set; }
public string? Arguments { get; set; }
public ProgramConfiguration() { }
public ProgramConfiguration(string? path, string? arguments = null)
{ {
public string? Path { get; set; } Path = path;
public string? Arguments { get; set; } Arguments = arguments;
public ProgramConfiguration() { }
public ProgramConfiguration(string? path, string? arguments = null)
{
Path = path;
Arguments = arguments;
}
} }
} }

View File

@@ -1,10 +1,9 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace FileTime.GuiApp.Configuration namespace FileTime.GuiApp.Configuration;
public class ProgramsConfiguration
{ {
public class ProgramsConfiguration public List<ProgramConfiguration> DefaultEditorPrograms { get; set; } = new();
{ public List<ProgramConfiguration> EditorPrograms { get; set; } = new();
public List<ProgramConfiguration> DefaultEditorPrograms { get; set; } = new();
public List<ProgramConfiguration> EditorPrograms { get; set; } = new();
}
} }

View File

@@ -1,8 +1,7 @@
namespace FileTime.GuiApp.Configuration namespace FileTime.GuiApp.Configuration;
public static class SectionNames
{ {
public static class SectionNames public const string KeybindingSectionName = "KeyBindings";
{ public const string ProgramsSectionName = "Programs";
public const string KeybindingSectionName = "KeyBindings";
public const string ProgramsSectionName = "Programs";
}
} }

View File

@@ -6,6 +6,10 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.13" /> <PackageReference Include="Avalonia" Version="0.10.13" />
</ItemGroup> </ItemGroup>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,40 +7,39 @@ using FileTime.GuiApp.Views;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace FileTime.GuiApp namespace FileTime.GuiApp;
public partial class App : Application
{ {
public partial class App : Application static App()
{ {
static App() DI.ServiceProvider ??= DependencyInjection
{ .RegisterDefaultServices()
DI.ServiceProvider ??= DependencyInjection .AddConfiguration()
.RegisterDefaultServices() .RegisterLogging()
.AddConfiguration() .RegisterServices()
.RegisterLogging() .AddViewModels()
.RegisterServices() .BuildServiceProvider()
.AddViewModels() .InitSerilog();
.BuildServiceProvider()
.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()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{ {
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) desktop.MainWindow = new MainWindow
{ {
desktop.MainWindow = new MainWindow DataContext = new MainWindowLoadingViewModel(),
{ };
DataContext = new MainWindowLoadingViewModel(),
};
}
base.OnFrameworkInitializationCompleted();
} }
base.OnFrameworkInitializationCompleted();
} }
} }

View File

@@ -9,6 +9,11 @@
<ApplicationIcon>filetime.ico</ApplicationIcon> <ApplicationIcon>filetime.ico</ApplicationIcon>
<Version>0.0.1</Version> <Version>0.0.1</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<Content Include="filetime.ico" /> <Content Include="filetime.ico" />
</ItemGroup> </ItemGroup>

View File

@@ -3,19 +3,19 @@ using System.IO;
using Avalonia; using Avalonia;
using Avalonia.ReactiveUI; using Avalonia.ReactiveUI;
namespace FileTime.GuiApp namespace FileTime.GuiApp;
public static class Program
{ {
public static class Program public static string AppDataRoot { get; }
public static string EnvironmentName { get; }
static Program()
{ {
public static string AppDataRoot { get; }
public static string EnvironmentName { get; }
static Program()
{
#if DEBUG #if DEBUG
EnvironmentName = "Development"; EnvironmentName = "Development";
AppDataRoot = Path.Combine(Environment.CurrentDirectory, "appdata"); AppDataRoot = Path.Combine(Environment.CurrentDirectory, "appdata");
#else #else
EnvironmentName = "Release"; EnvironmentName = "Release";
@@ -43,20 +43,19 @@ namespace FileTime.GuiApp
if (appDataRoot == null) throw new UnauthorizedAccessException(); if (appDataRoot == null) throw new UnauthorizedAccessException();
AppDataRoot = appDataRoot; AppDataRoot = appDataRoot;
#endif #endif
}
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.LogToTrace();
} }
}
// Initialization code. Don't use any Avalonia, third-party APIs or any
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
// yet and stuff might break.
[STAThread]
public static void Main(string[] args) => BuildAvaloniaApp()
.StartWithClassicDesktopLifetime(args);
// Avalonia configuration, don't remove; also used by visual designer.
public static AppBuilder BuildAvaloniaApp()
=> AppBuilder.Configure<App>()
.UsePlatformDetect()
.UseReactiveUI()
.LogToTrace();
}

View File

@@ -11,70 +11,69 @@ using Microsoft.Extensions.DependencyInjection;
using Serilog; using Serilog;
using Serilog.Configuration; using Serilog.Configuration;
namespace FileTime.GuiApp namespace FileTime.GuiApp;
public static class Startup
{ {
public static class Startup internal static IServiceCollection AddViewModels(this IServiceCollection serviceCollection)
{ {
internal static IServiceCollection AddViewModels(this IServiceCollection serviceCollection) return serviceCollection
{ .AddSingleton<MainWindowViewModel>()
return serviceCollection .AddSingleton<GuiAppState>()
.AddSingleton<MainWindowViewModel>() .AddSingleton<IAppState, GuiAppState>(s => s.GetRequiredService<GuiAppState>())
.AddSingleton<GuiAppState>() .AddSingleton<IGuiAppState, GuiAppState>(s => s.GetRequiredService<GuiAppState>());
.AddSingleton<IAppState, GuiAppState>(s => s.GetRequiredService<GuiAppState>()) }
.AddSingleton<IGuiAppState, GuiAppState>(s => s.GetRequiredService<GuiAppState>()); internal static IServiceCollection RegisterServices(this IServiceCollection serviceCollection)
} {
internal static IServiceCollection RegisterServices(this IServiceCollection serviceCollection) return serviceCollection
{ .AddSingleton<IRxSchedulerService, AvaloniaRxSchedulerService>()
return serviceCollection .AddSingleton<IKeyInputHandlerService, KeyInputHandlerService>()
.AddSingleton<IRxSchedulerService, AvaloniaRxSchedulerService>() .AddSingleton<IDefaultModeKeyInputHandler, DefaultModeKeyInputHandler>()
.AddSingleton<IKeyInputHandlerService, KeyInputHandlerService>() .AddSingleton<IKeyboardConfigurationService, KeyboardConfigurationService>()
.AddSingleton<IDefaultModeKeyInputHandler, DefaultModeKeyInputHandler>() .AddSingleton<IRapidTravelModeKeyInputHandler, RapidTravelModeKeyInputHandler>();
.AddSingleton<IKeyboardConfigurationService, KeyboardConfigurationService>() }
.AddSingleton<IRapidTravelModeKeyInputHandler, RapidTravelModeKeyInputHandler>();
}
internal static IServiceCollection RegisterLogging(this IServiceCollection serviceCollection) internal static IServiceCollection RegisterLogging(this IServiceCollection serviceCollection)
{ {
return serviceCollection.AddLogging(loggingBuilder => return serviceCollection.AddLogging(loggingBuilder =>
loggingBuilder.AddSerilog(dispose: true) loggingBuilder.AddSerilog(dispose: true)
); );
} }
internal static IServiceCollection AddConfiguration(this IServiceCollection serviceCollection) internal static IServiceCollection AddConfiguration(this IServiceCollection serviceCollection)
{ {
var configuration = new ConfigurationBuilder() var configuration = new ConfigurationBuilder()
.AddInMemoryCollection(MainConfiguration.Configuration) .AddInMemoryCollection(MainConfiguration.Configuration)
.AddJsonFile("appsettings.json", optional: true) .AddJsonFile("appsettings.json", optional: true)
.AddJsonFile($"appsettings.{Program.EnvironmentName}.json", true) .AddJsonFile($"appsettings.{Program.EnvironmentName}.json", true)
.Build(); .Build();
return serviceCollection return serviceCollection
.Configure<ProgramsConfiguration>(configuration.GetSection(SectionNames.ProgramsSectionName)) .Configure<ProgramsConfiguration>(configuration.GetSection(SectionNames.ProgramsSectionName))
.Configure<KeyBindingConfiguration>(configuration.GetSection(SectionNames.KeybindingSectionName)) .Configure<KeyBindingConfiguration>(configuration.GetSection(SectionNames.KeybindingSectionName))
.AddSingleton<IConfiguration>(configuration); .AddSingleton<IConfiguration>(configuration);
} }
internal static IServiceProvider InitSerilog(this IServiceProvider serviceProvider) internal static IServiceProvider InitSerilog(this IServiceProvider serviceProvider)
{ {
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(serviceProvider.GetService<IConfiguration>()) .ReadFrom.Configuration(serviceProvider.GetService<IConfiguration>())
.Enrich.FromLogContext() .Enrich.FromLogContext()
.WriteTo.File( .WriteTo.File(
Path.Combine(Program.AppDataRoot, "logs", "appLog.log"), Path.Combine(Program.AppDataRoot, "logs", "appLog.log"),
fileSizeLimitBytes: 10 * 1024 * 1024, fileSizeLimitBytes: 10 * 1024 * 1024,
rollOnFileSizeLimit: true, rollOnFileSizeLimit: true,
rollingInterval: RollingInterval.Day) rollingInterval: RollingInterval.Day)
.WriteTo.MessageBoxSink(serviceProvider) .WriteTo.MessageBoxSink(serviceProvider)
.CreateLogger(); .CreateLogger();
return serviceProvider; return serviceProvider;
} }
internal static LoggerConfiguration MessageBoxSink( internal static LoggerConfiguration MessageBoxSink(
this LoggerSinkConfiguration loggerConfiguration, this LoggerSinkConfiguration loggerConfiguration,
IServiceProvider serviceProvider) IServiceProvider serviceProvider)
{ {
return loggerConfiguration.Sink(serviceProvider.GetService<ToastMessageSink>()); return loggerConfiguration.Sink(serviceProvider.GetService<ToastMessageSink>());
}
} }
} }

View File

@@ -6,6 +6,10 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<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" /> <ProjectReference Include="..\FileTime.GuiApp.Abstractions\FileTime.GuiApp.Abstractions.csproj" />

View File

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

View File

@@ -1,54 +1,53 @@
using System.Globalization; using System.Globalization;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters namespace FileTime.GuiApp.Converters;
public enum ComparisonCondition
{ {
public enum ComparisonCondition Equal,
GreaterThan,
LessThan,
LessThanOrEqual,
NotEqual,
GreaterThanOrEqual
}
public class CompareConverter : IValueConverter
{
public ComparisonCondition ComparisonCondition { get; set; } = ComparisonCondition.Equal;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
Equal, return Compare(value, parameter);
GreaterThan,
LessThan,
LessThanOrEqual,
NotEqual,
GreaterThanOrEqual
} }
public class CompareConverter : IValueConverter public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
public ComparisonCondition ComparisonCondition { get; set; } = ComparisonCondition.Equal; throw new NotImplementedException();
}
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) private bool Compare(object? value, object? parameter)
{
if (ComparisonCondition == ComparisonCondition.GreaterThan)
{ {
return Compare(value, parameter); if (value is int valueInt && (parameter is int parameterInt || int.TryParse(parameter?.ToString(), out parameterInt))) return valueInt > parameterInt;
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble > parameterDouble;
else throw new NotSupportedException();
}
else if (ComparisonCondition == ComparisonCondition.NotEqual)
{
if (value is int valueInt && (parameter is int parameterInt || int.TryParse(parameter?.ToString(), out parameterInt))) return valueInt != parameterInt;
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble != parameterDouble;
return value != parameter;
}
else if (ComparisonCondition == ComparisonCondition.Equal)
{
if (value is int valueInt && (parameter is int parameterInt || int.TryParse(parameter?.ToString(), out parameterInt))) return valueInt == parameterInt;
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble == parameterDouble;
else if (value?.GetType().IsEnum ?? false && Enum.TryParse(value.GetType(), parameter?.ToString(), out var _)) return value.ToString() == parameter?.ToString();
} }
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) return value == parameter;
{
throw new NotImplementedException();
}
private bool Compare(object? value, object? parameter)
{
if (ComparisonCondition == ComparisonCondition.GreaterThan)
{
if (value is int valueInt && (parameter is int parameterInt || int.TryParse(parameter?.ToString(), out parameterInt))) return valueInt > parameterInt;
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble > parameterDouble;
else throw new NotSupportedException();
}
else if (ComparisonCondition == ComparisonCondition.NotEqual)
{
if (value is int valueInt && (parameter is int parameterInt || int.TryParse(parameter?.ToString(), out parameterInt))) return valueInt != parameterInt;
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble != parameterDouble;
return value != parameter;
}
else if (ComparisonCondition == ComparisonCondition.Equal)
{
if (value is int valueInt && (parameter is int parameterInt || int.TryParse(parameter?.ToString(), out parameterInt))) return valueInt == parameterInt;
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble == parameterDouble;
else if (value?.GetType().IsEnum ?? false && Enum.TryParse(value.GetType(), parameter?.ToString(), out var _)) return value.ToString() == parameter?.ToString();
}
return value == parameter;
}
} }
} }

View File

@@ -1,18 +1,17 @@
using System.Globalization; using System.Globalization;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters namespace FileTime.GuiApp.Converters;
{
public class DateTimeConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
value is DateTime dateTime && parameter is string parameterS
? dateTime.ToString(parameterS)
: value;
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) public class DateTimeConverter : IValueConverter
{ {
throw new NotImplementedException(); public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) =>
} value is DateTime dateTime && parameter is string parameterS
? dateTime.ToString(parameterS)
: value;
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
} }
} }

View File

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

View File

@@ -1,47 +1,46 @@
using System.Globalization; using System.Globalization;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters namespace FileTime.GuiApp.Converters;
public class FormatSizeConverter : IValueConverter
{ {
public class FormatSizeConverter : IValueConverter private const long OneKiloByte = 1024;
private const long OneMegaByte = OneKiloByte * 1024;
private const long OneGigaByte = OneMegaByte * 1024;
private const long OneTerraByte = OneGigaByte * 1024;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
private const long OneKiloByte = 1024; return (value, int.TryParse(parameter?.ToString(), out var prec)) switch
private const long OneMegaByte = OneKiloByte * 1024;
private const long OneGigaByte = OneMegaByte * 1024;
private const long OneTerraByte = OneGigaByte * 1024;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
return (value, int.TryParse(parameter?.ToString(), out var prec)) switch (long size, true) => ToSizeString(size, prec),
{ (long size, false) => ToSizeString(size),
(long size, true) => ToSizeString(size, prec), (null, _) => "...",
(long size, false) => ToSizeString(size), _ => value
(null, _) => "...", };
_ => value }
};
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
public static string ToSizeString(long fileSize, int precision = 1)
{
var fileSizeD = (decimal)fileSize;
var (size, suffix) = fileSize switch
{ {
throw new NotImplementedException(); > OneTerraByte => (fileSizeD / OneTerraByte, "T"),
} > OneGigaByte => (fileSizeD / OneGigaByte, "G"),
> OneMegaByte => (fileSizeD / OneMegaByte, "M"),
> OneKiloByte => (fileSizeD / OneKiloByte, "K"),
_ => (fileSizeD, "B")
};
public static string ToSizeString(long fileSize, int precision = 1) var result = string.Format("{0:N" + precision + "}", size).Replace(',', '.');
{
var fileSizeD = (decimal)fileSize;
var (size, suffix) = fileSize switch
{
> OneTerraByte => (fileSizeD / OneTerraByte, "T"),
> OneGigaByte => (fileSizeD / OneGigaByte, "G"),
> OneMegaByte => (fileSizeD / OneMegaByte, "M"),
> OneKiloByte => (fileSizeD / OneKiloByte, "K"),
_ => (fileSizeD, "B")
};
var result = string.Format("{0:N" + precision + "}", size).Replace(',', '.'); if (result.Contains('.')) result = result.TrimEnd('0').TrimEnd('.');
return result + " " + suffix;
if (result.Contains('.')) result = result.TrimEnd('0').TrimEnd('.');
return result + " " + suffix;
}
} }
} }

View File

@@ -3,22 +3,21 @@ using Avalonia.Data.Converters;
using FileTime.App.Core.Services; using FileTime.App.Core.Services;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace FileTime.GuiApp.Converters namespace FileTime.GuiApp.Converters;
public class GetFileExtensionConverter : IValueConverter
{ {
public class GetFileExtensionConverter : IValueConverter private IItemNameConverterService? _itemNameConverterService;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
private IItemNameConverterService? _itemNameConverterService; if (value is not string fullName) return value;
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) _itemNameConverterService ??= DI.ServiceProvider.GetRequiredService<IItemNameConverterService>();
{
if (value is not string fullName) return value;
_itemNameConverterService ??= DI.ServiceProvider.GetRequiredService<IItemNameConverterService>();
return _itemNameConverterService.GetFileExtension(fullName); return _itemNameConverterService.GetFileExtension(fullName);
} }
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
}
} }
} }

View File

@@ -3,39 +3,38 @@ using Avalonia.Data.Converters;
using Avalonia.Media; using Avalonia.Media;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
namespace FileTime.GuiApp.Converters namespace FileTime.GuiApp.Converters;
public class ItemViewModeToBrushConverter : IValueConverter
{ {
public class ItemViewModeToBrushConverter : IValueConverter public Brush? DefaultBrush { get; set; }
public Brush? AlternativeBrush { get; set; }
public Brush? SelectedBrush { get; set; }
public Brush? MarkedBrush { get; set; }
public Brush? MarkedSelectedBrush { get; set; }
public Brush? MarkedAlternativeBrush { get; set; }
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
public Brush? DefaultBrush { get; set; } if (value is ItemViewMode viewMode)
public Brush? AlternativeBrush { get; set; }
public Brush? SelectedBrush { get; set; }
public Brush? MarkedBrush { get; set; }
public Brush? MarkedSelectedBrush { get; set; }
public Brush? MarkedAlternativeBrush { get; set; }
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
if (value is ItemViewMode viewMode) return viewMode switch
{ {
return viewMode switch ItemViewMode.Default => DefaultBrush,
{ ItemViewMode.Alternative => AlternativeBrush,
ItemViewMode.Default => DefaultBrush, ItemViewMode.Selected => SelectedBrush,
ItemViewMode.Alternative => AlternativeBrush, ItemViewMode.Marked => MarkedBrush,
ItemViewMode.Selected => SelectedBrush, ItemViewMode.MarkedSelected => MarkedSelectedBrush,
ItemViewMode.Marked => MarkedBrush, ItemViewMode.MarkedAlternative => MarkedAlternativeBrush,
ItemViewMode.MarkedSelected => MarkedSelectedBrush, _ => throw new NotImplementedException()
ItemViewMode.MarkedAlternative => MarkedAlternativeBrush, };
_ => throw new NotImplementedException()
};
}
return value;
} }
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) return value;
{ }
throw new NotImplementedException();
} public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
} }
} }

View File

@@ -3,35 +3,34 @@ using Avalonia.Data.Converters;
using FileTime.App.Core.Models.Enums; using FileTime.App.Core.Models.Enums;
using FileTime.App.Core.ViewModels; using FileTime.App.Core.ViewModels;
namespace FileTime.GuiApp.Converters namespace FileTime.GuiApp.Converters;
public class ItemViewModelIsAttributeTypeConverter : IValueConverter
{ {
public class ItemViewModelIsAttributeTypeConverter : IValueConverter public bool Invert { get; set; }
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
public bool Invert { get; set; } var attributeType = GetAttributeType(value);
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) if (parameter == null) return attributeType;
{ var result = parameter is ItemAttributeType targetAttribute && attributeType == targetAttribute;
var attributeType = GetAttributeType(value); if (Invert && parameter is ItemAttributeType) result = !result;
if (parameter == null) return attributeType; return result;
var result = parameter is ItemAttributeType targetAttribute && attributeType == targetAttribute; }
if (Invert && parameter is ItemAttributeType) result = !result;
return result;
}
private static ItemAttributeType? GetAttributeType(object? value) private static ItemAttributeType? GetAttributeType(object? value)
{
return value switch
{ {
return value switch IFileViewModel => ItemAttributeType.File,
{ IContainerSizeContainerViewModel => ItemAttributeType.SizeContainer,
IFileViewModel => ItemAttributeType.File, IElementViewModel => ItemAttributeType.Element,
IContainerSizeContainerViewModel => ItemAttributeType.SizeContainer, IContainerViewModel => ItemAttributeType.Container,
IElementViewModel => ItemAttributeType.Element, _ => null
IContainerViewModel => ItemAttributeType.Container, };
_ => null }
};
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
}
} }
} }

View File

@@ -4,112 +4,111 @@ using Avalonia.Media;
using FileTime.App.Core.Models; using FileTime.App.Core.Models;
using FileTime.GuiApp.ViewModels; using FileTime.GuiApp.ViewModels;
namespace FileTime.GuiApp.Converters namespace FileTime.GuiApp.Converters;
public class NamePartShrinkerConverter : IMultiValueConverter
{ {
public class NamePartShrinkerConverter : IMultiValueConverter private const int PixelPerChar = 8;
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{ {
private const int PixelPerChar = 8; if (values.Count > 0 && values[0] is IList<ItemNamePart> nameParts)
public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
{ {
if (values.Count > 0 && values[0] is IList<ItemNamePart> nameParts) var attributeWidth = values[2] is bool b && b ? 340 : 0;
var newNameParts = nameParts;
if (values.Count > 1 && values[1] is double width && width > 0)
{ {
var attributeWidth = values[2] is bool b && b ? 340 : 0; newNameParts = GetNamePartsForWidth(nameParts, width - attributeWidth);
var newNameParts = nameParts;
if (values.Count > 1 && values[1] is double width && width > 0)
{
newNameParts = GetNamePartsForWidth(nameParts, width - attributeWidth);
}
return newNameParts.Select(p => new ItemNamePartViewModel(p.Text, p.IsSpecial ? TextDecorations.Underline : null)).ToList();
} }
return null;
return newNameParts.Select(p => new ItemNamePartViewModel(p.Text, p.IsSpecial ? TextDecorations.Underline : null)).ToList();
}
return null;
}
private static List<ItemNamePart> GetNamePartsForWidth(IList<ItemNamePart> nameParts, double maxWidth)
{
//Best case, we are in the range
var textLength = nameParts.Select(p => p.Text.Length).Sum();
if (textLength * PixelPerChar <= maxWidth)
{
return nameParts.ToList();
} }
private static List<ItemNamePart> GetNamePartsForWidth(IList<ItemNamePart> nameParts, double maxWidth) //Trying at least with the special parts
var newNameParts = new ItemNamePart?[nameParts.Count];
for (var i = 0; i < nameParts.Count; i++)
{ {
//Best case, we are in the range if (nameParts[i].IsSpecial)
var textLength = nameParts.Select(p => p.Text.Length).Sum();
if (textLength * PixelPerChar <= maxWidth)
{ {
return nameParts.ToList(); newNameParts[i] = nameParts[i];
} }
//Trying at least with the special parts
var newNameParts = new ItemNamePart?[nameParts.Count];
for (var i = 0; i < nameParts.Count; i++)
{
if (nameParts[i].IsSpecial)
{
newNameParts[i] = nameParts[i];
}
}
return GetNamePartsForWidthOptimistic(nameParts, newNameParts, maxWidth);
} }
private static List<ItemNamePart> GetNamePartsForWidthOptimistic(IList<ItemNamePart> nameParts, ItemNamePart?[] newNameParts, double maxWidth) return GetNamePartsForWidthOptimistic(nameParts, newNameParts, maxWidth);
}
private static List<ItemNamePart> GetNamePartsForWidthOptimistic(IList<ItemNamePart> nameParts, ItemNamePart?[] newNameParts, double maxWidth)
{
var trimmedIndexes = new List<int>();
for (var i = 0; i < newNameParts.Length; i++)
{ {
var trimmedIndexes = new List<int>(); if (newNameParts[i] == null)
for (var i = 0; i < newNameParts.Length; i++)
{ {
if (newNameParts[i] == null) trimmedIndexes.Add(i);
{ newNameParts[i] = new ItemNamePart("...");
trimmedIndexes.Add(i);
newNameParts[i] = new ItemNamePart("...");
}
} }
var textLength = newNameParts.Select(p => p?.Text.Length ?? 0).Sum();
if (textLength * PixelPerChar > maxWidth)
{
return GetNamePartsForWidthPessimistic(nameParts, maxWidth);
}
foreach (var trimmedIndex in trimmedIndexes)
{
var baseTextLength = newNameParts.Select((p, i) => i == trimmedIndex ? 0 : (p?.Text.Length ?? 0)).Sum();
var proposedText = nameParts[trimmedIndex].Text;
var trimmed = false;
while ((baseTextLength + proposedText.Length + (trimmed ? 3 : 0)) * PixelPerChar > maxWidth)
{
proposedText = proposedText[0..^1];
trimmed = true;
}
newNameParts[trimmedIndex] = new ItemNamePart(proposedText + (trimmed ? "..." : ""));
if (trimmed) break;
}
return newNameParts.Where(f => f is not null).ToList()!;
} }
private static List<ItemNamePart> GetNamePartsForWidthPessimistic(IList<ItemNamePart> nameParts, double maxWidth) var textLength = newNameParts.Select(p => p?.Text.Length ?? 0).Sum();
if (textLength * PixelPerChar > maxWidth)
{ {
var newNameParts = new List<ItemNamePart>(nameParts); return GetNamePartsForWidthPessimistic(nameParts, maxWidth);
foreach (var namePart in nameParts)
{
var baseTextLength = newNameParts.Select(p => p.Text.Length).Sum();
var proposedText = namePart.Text;
var trimmed = false;
while ((baseTextLength + 3) * PixelPerChar > maxWidth && proposedText != "")
{
proposedText = proposedText[0..^1];
trimmed = true;
}
if (!string.IsNullOrWhiteSpace(proposedText)) newNameParts.Add(new ItemNamePart(proposedText, namePart.IsSpecial));
if (trimmed) break;
}
if (newNameParts.Last().IsSpecial)
{
newNameParts.Add(new ItemNamePart("..."));
}
else
{
var last = newNameParts.Last();
last.Text += "...";
}
return newNameParts;
} }
foreach (var trimmedIndex in trimmedIndexes)
{
var baseTextLength = newNameParts.Select((p, i) => i == trimmedIndex ? 0 : (p?.Text.Length ?? 0)).Sum();
var proposedText = nameParts[trimmedIndex].Text;
var trimmed = false;
while ((baseTextLength + proposedText.Length + (trimmed ? 3 : 0)) * PixelPerChar > maxWidth)
{
proposedText = proposedText[0..^1];
trimmed = true;
}
newNameParts[trimmedIndex] = new ItemNamePart(proposedText + (trimmed ? "..." : ""));
if (trimmed) break;
}
return newNameParts.Where(f => f is not null).ToList()!;
}
private static List<ItemNamePart> GetNamePartsForWidthPessimistic(IList<ItemNamePart> nameParts, double maxWidth)
{
var newNameParts = new List<ItemNamePart>(nameParts);
foreach (var namePart in nameParts)
{
var baseTextLength = newNameParts.Select(p => p.Text.Length).Sum();
var proposedText = namePart.Text;
var trimmed = false;
while ((baseTextLength + 3) * PixelPerChar > maxWidth && proposedText != "")
{
proposedText = proposedText[0..^1];
trimmed = true;
}
if (!string.IsNullOrWhiteSpace(proposedText)) newNameParts.Add(new ItemNamePart(proposedText, namePart.IsSpecial));
if (trimmed) break;
}
if (newNameParts.Last().IsSpecial)
{
newNameParts.Add(new ItemNamePart("..."));
}
else
{
var last = newNameParts.Last();
last.Text += "...";
}
return newNameParts;
} }
} }

View File

@@ -1,27 +1,26 @@
using System.Globalization; using System.Globalization;
using Avalonia.Data.Converters; using Avalonia.Data.Converters;
namespace FileTime.GuiApp.Converters namespace FileTime.GuiApp.Converters;
public class SplitStringConverter : IValueConverter
{ {
public class SplitStringConverter : IValueConverter public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{ {
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) if (value is string path && parameter is string separator)
{ {
if (value is string path && parameter is string separator) return path.Split(separator);
{ }
return path.Split(separator); else if (value is string path2 && parameter is char separator2)
} {
else if (value is string path2 && parameter is char separator2) return path2.Split(separator2);
{
return path2.Split(separator2);
}
return value;
} }
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) return value;
{ }
throw new NotImplementedException();
} public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
} }
} }

Some files were not shown because too many files have changed in this diff Show More