MainPageViewModel refactor, keybindings from config

This commit is contained in:
2022-02-08 17:21:20 +01:00
parent dda636fd9c
commit 70180b9f12
19 changed files with 1534 additions and 1316 deletions

View File

@@ -2,34 +2,58 @@ namespace FileTime.App.Core.Command
{ {
public enum Commands public enum Commands
{ {
None,
AutoRefresh,
ChangeTimelineMode,
CloseTab, CloseTab,
Copy, Copy,
CopyPath,
CreateContainer, CreateContainer,
CreateElement, CreateElement,
Cut, Cut,
EnterRapidTravel, EnterRapidTravel,
GoToHome, GoToHome,
GoToPath,
GoToProvider, GoToProvider,
GoToRoot, GoToRoot,
GoUp, GoUp,
Delete, HardDelete,
Mark,
MoveCursorDown, MoveCursorDown,
MoveCursorDownPage, MoveCursorDownPage,
MoveCursorUp, MoveCursorUp,
MoveCursorUpPage, MoveCursorUpPage,
MoveToBottom,
MoveToFirst, MoveToFirst,
MoveToLast, MoveToLast,
MoveToTop, NextTimelineBlock,
NextTimelineCommand,
Open, Open,
OpenInFileBrowser,
OpenOrRun, OpenOrRun,
PasteMerge, PasteMerge,
PasteOverwrite, PasteOverwrite,
PasteSkip, PasteSkip,
Select, PreviousTimelineBlock,
ToggleHidden, PreviousTimelineCommand,
Rename,
Dummy,
Refresh, Refresh,
Rename,
RunCommand,
ShowAllShotcut,
SoftDelete,
SwitchToLastTab,
SwitchToTab1,
SwitchToTab2,
SwitchToTab3,
SwitchToTab4,
SwitchToTab5,
SwitchToTab6,
SwitchToTab7,
SwitchToTab8,
TimelinePause,
TimelineRefresh,
TimelineStart,
ToggleAdvancedIcons,
ToggleHidden,
} }
} }

View File

@@ -90,7 +90,7 @@ namespace FileTime.ConsoleUI.App
new CommandBinding("open", Commands.Open, new[] { new ConsoleKeyInfo('→', ConsoleKey.RightArrow, false, false, false) }, Open), new CommandBinding("open", Commands.Open, new[] { new ConsoleKeyInfo('→', ConsoleKey.RightArrow, false, false, false) }, Open),
new CommandBinding( new CommandBinding(
"go to top", "go to top",
Commands.MoveToTop, Commands.MoveToFirst,
new[] new[]
{ {
new ConsoleKeyInfo('g', ConsoleKey.G, false, false, false), new ConsoleKeyInfo('g', ConsoleKey.G, false, false, false),
@@ -99,7 +99,7 @@ namespace FileTime.ConsoleUI.App
MoveCursorToTop), MoveCursorToTop),
new CommandBinding( new CommandBinding(
"go to bottom", "go to bottom",
Commands.MoveToBottom, Commands.MoveToLast,
new[] new[]
{ {
new ConsoleKeyInfo('G', ConsoleKey.G, true, false, false) new ConsoleKeyInfo('G', ConsoleKey.G, true, false, false)
@@ -114,7 +114,7 @@ namespace FileTime.ConsoleUI.App
new ConsoleKeyInfo('h', ConsoleKey.H, false, false, false) new ConsoleKeyInfo('h', ConsoleKey.H, false, false, false)
}, },
ToggleHidden), ToggleHidden),
new CommandBinding("select", Commands.Select, new[] { new ConsoleKeyInfo(' ', ConsoleKey.Spacebar, false, false, false) }, Select), new CommandBinding("select", Commands.Mark, new[] { new ConsoleKeyInfo(' ', ConsoleKey.Spacebar, false, false, false) }, Select),
new CommandBinding( new CommandBinding(
"copy", "copy",
Commands.Copy, Commands.Copy,

View File

@@ -134,6 +134,7 @@
<converters:ItemViewModelIsAttibuteTypeConverter x:Key="ItemViewModelIsAttibuteTypeConverter"/> <converters:ItemViewModelIsAttibuteTypeConverter x:Key="ItemViewModelIsAttibuteTypeConverter"/>
<converters:ItemViewModelIsAttibuteTypeConverter x:Key="ItemViewModelIsNotAttibuteTypeConverter" Invert="true"/> <converters:ItemViewModelIsAttibuteTypeConverter x:Key="ItemViewModelIsNotAttibuteTypeConverter" Invert="true"/>
<converters:GetFileExtensionConverter x:Key="GetFileExtensionConverter"/> <converters:GetFileExtensionConverter x:Key="GetFileExtensionConverter"/>
<converters:CommandToCommandNameConverter x:Key="CommandToCommandNameConverter"/>
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>
@@ -186,15 +187,5 @@
<Style Selector="Border.SelectedTimelineCommand"> <Style Selector="Border.SelectedTimelineCommand">
<Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ForegroundBrush}"/>
</Style> </Style>
<!--Style Selector="MenuItem">
<Setter Property="Template">
<DataTemplate>
<Grid ColumnDefinitions="20,*">
<TextBlock Grid.Column="1" Text="{Binding Header}" />
</Grid>
</DataTemplate>
</Setter>
</Style-->
</Application.Styles> </Application.Styles>
</Application> </Application>

View File

@@ -7,6 +7,10 @@ using FileTime.App.Core.Tab;
using System.Threading.Tasks; using System.Threading.Tasks;
using FileTime.Core.Models; using FileTime.Core.Models;
using System.Threading; using System.Threading;
using FileTime.Avalonia.Configuration;
using FileTime.Avalonia.Misc;
using FileTime.Core.Extensions;
using FileTime.Avalonia.ViewModels;
namespace FileTime.Avalonia.Application namespace FileTime.Avalonia.Application
{ {
@@ -26,6 +30,28 @@ namespace FileTime.Avalonia.Application
[Property] [Property]
private string _rapidTravelText = ""; private string _rapidTravelText = "";
[Property]
private List<CommandBindingConfiguration> _possibleCommands = new();
[Property]
private List<InputElementWrapper> _inputs;
[Property]
private string _messageBoxText;
[Property]
private ObservableCollection<string> _popupTexts = new();
[Property]
private bool _isAllShortcutVisible;
[Property]
private bool _noCommandFound;
public List<KeyConfig> PreviousKeys { get; } = new();
public ObservableCollection<ParallelCommandsViewModel> TimelineCommands { get; } = new();
partial void OnInitialize() partial void OnInitialize()
{ {
_tabs.CollectionChanged += TabsChanged; _tabs.CollectionChanged += TabsChanged;
@@ -66,7 +92,7 @@ namespace FileTime.Avalonia.Application
private void SelectedTabChanged() private void SelectedTabChanged()
{ {
foreach(var tab in Tabs) foreach (var tab in Tabs)
{ {
tab.IsSelected = tab == SelectedTab; tab.IsSelected = tab == SelectedTab;
} }
@@ -99,5 +125,16 @@ namespace FileTime.Avalonia.Application
} }
} }
} }
public async Task ExitRapidTravelMode()
{
ViewMode = ViewMode.Default;
PreviousKeys.Clear();
PossibleCommands = new();
RapidTravelText = "";
await SelectedTab.OpenContainer(await SelectedTab.CurrentLocation.Container.WithoutVirtualContainer(MainPageViewModel.RAPIDTRAVEL));
}
} }
} }

View File

@@ -1,4 +1,4 @@
using FileTime.App.Core.Command; /* using FileTime.App.Core.Command;
using FileTime.Avalonia.Misc; using FileTime.Avalonia.Misc;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -14,11 +14,11 @@ namespace FileTime.Avalonia.Command
public string Name { get; } public string Name { get; }
public Commands? Command { get; } public Commands? Command { get; }
public KeyWithModifiers[] Keys { get; } public KeyConfig[] Keys { get; }
public string KeysDisplayText => GetKeysDisplayText(); public string KeysDisplayText => GetKeysDisplayText();
public CommandBinding(string name, Commands? command, KeyWithModifiers[] keys, Func<Task> commandHandler) public CommandBinding(string name, Commands? command, KeyConfig[] keys, Func<Task> commandHandler)
{ {
_commandHandler = commandHandler; _commandHandler = commandHandler;
Name = name; Name = name;
@@ -48,7 +48,7 @@ namespace FileTime.Avalonia.Command
return s; return s;
} }
private static string AddKeyWithCtrlOrAlt(KeyWithModifiers key, string currentText, Func<KeyWithModifiers, string, bool, string> keyProcessor) private static string AddKeyWithCtrlOrAlt(KeyConfig key, string currentText, Func<KeyConfig, string, bool, string> keyProcessor)
{ {
var s = ""; var s = "";
@@ -65,7 +65,7 @@ namespace FileTime.Avalonia.Command
return s; return s;
} }
private static string AddSpecialKey(KeyWithModifiers key, string currentText, bool wasCtrlOrAlt) private static string AddSpecialKey(KeyConfig key, string currentText, bool wasCtrlOrAlt)
{ {
var s = ""; var s = "";
@@ -77,3 +77,4 @@ namespace FileTime.Avalonia.Command
} }
} }
} }
*/

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Input;
using FileTime.App.Core.Command;
namespace FileTime.Avalonia.Configuration
{
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)
{
Keys = new List<KeyConfig>(keys);
Command = command;
}
public CommandBindingConfiguration(Commands command, KeyConfig key)
{
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();
if (keyString.Length == 1)
{
s += AddKeyWithCtrlOrAlt(k, s, (_, _, _) => k.Shift ? keyString.ToUpper() : keyString.ToLower());
}
else
{
s += AddKeyWithCtrlOrAlt(k, s, AddSpecialKey);
}
}
return s;
}
private static string AddKeyWithCtrlOrAlt(KeyConfig key, string currentText, Func<KeyConfig, string, bool, string> keyProcessor)
{
var s = "";
bool ctrlOrAlt = key.Ctrl || key.Alt;
if (ctrlOrAlt && currentText.Length > 0 && currentText.Last() != ' ') s += " ";
if (key.Ctrl) s += "CTRL+";
if (key.Alt) s += "ALT+";
s += keyProcessor(key, currentText, ctrlOrAlt);
if (ctrlOrAlt) s += " ";
return s;
}
private static string AddSpecialKey(KeyConfig key, string currentText, bool wasCtrlOrAlt)
{
var s = "";
if (currentText.Length > 0 && currentText.Last() != ' ' && !wasCtrlOrAlt) s += " ";
s += key.Key.ToString();
if (!wasCtrlOrAlt) s += " ";
return s;
}
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace FileTime.Avalonia.Configuration
{
public class KeyBindingConfiguration
{
public bool UseDefaultBindings { get; set; } = true;
public List<CommandBindingConfiguration> DefaultKeyBindings { get; set; } = new();
public List<CommandBindingConfiguration> KeyBindings { get; set; } = new();
}
}

View File

@@ -0,0 +1,22 @@
using Avalonia.Input;
namespace FileTime.Avalonia.Configuration
{
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)
{
Key = key;
Shift = shift;
Alt = alt;
Ctrl = ctrl;
}
}
}

View File

@@ -1,9 +1,101 @@
using Avalonia.Input;
using FileTime.App.Core.Command;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace FileTime.Avalonia.Configuration namespace FileTime.Avalonia.Configuration
{ {
public static class MainConfiguration public static class MainConfiguration
{ {
public static readonly Dictionary<string, string> Configuration = new(); private static readonly Lazy<List<CommandBindingConfiguration>> _defaultKeybindings = new(InitDefaultKeyBindings);
internal const string KeybindingBaseConfigKey = "KeyBindings";
public static Dictionary<string, string> Configuration { get; }
static MainConfiguration()
{
Configuration = new();
PopulateDefaultKeyBindings(Configuration, _defaultKeybindings.Value, KeybindingBaseConfigKey + ":" + nameof(KeyBindingConfiguration.DefaultKeyBindings));
}
private static void PopulateDefaultKeyBindings(Dictionary<string, string> keybindings, List<CommandBindingConfiguration> commandBindingConfigs, string basePath)
{
for (var i = 0; i < commandBindingConfigs.Count; i++)
{
var baseKey = basePath + $":[{i}]:";
var commandBindingConfig = commandBindingConfigs[i];
keybindings.Add(baseKey + nameof(CommandBindingConfiguration.Command), commandBindingConfig.Command.ToString());
for (var j = 0; j < commandBindingConfig.Keys.Count; j++)
{
var key = commandBindingConfig.Keys[j];
var keyBaseKey = baseKey + $"keys:[{j}]:";
keybindings.Add(keyBaseKey + nameof(KeyConfig.Key), key.Key.ToString());
keybindings.Add(keyBaseKey + nameof(KeyConfig.Shift), key.Shift.ToString());
keybindings.Add(keyBaseKey + nameof(KeyConfig.Alt), key.Alt.ToString());
keybindings.Add(keyBaseKey + nameof(KeyConfig.Ctrl), key.Ctrl.ToString());
}
}
}
private static List<CommandBindingConfiguration> InitDefaultKeyBindings()
{
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.CloseTab, Key.Q),
new CommandBindingConfiguration(Commands.Copy, new[] { Key.Y, Key.Y }),
new CommandBindingConfiguration(Commands.CopyPath, new[] { Key.C, Key.P }),
new CommandBindingConfiguration(Commands.CreateContainer, Key.F7),
new CommandBindingConfiguration(Commands.CreateContainer, new[] { Key.C, Key.C }),
new CommandBindingConfiguration(Commands.CreateElement, new[] { Key.C, Key.E }),
new CommandBindingConfiguration(Commands.Cut, new[] { Key.D, Key.D }),
new CommandBindingConfiguration(Commands.EnterRapidTravel, new KeyConfig(Key.OemComma, shift: true)),
new CommandBindingConfiguration(Commands.GoToHome, new[] { Key.G, Key.H }),
new CommandBindingConfiguration(Commands.GoToPath, new KeyConfig(Key.OemComma, ctrl: true)),
new CommandBindingConfiguration(Commands.GoToPath, new[] { Key.G, Key.P }),
new CommandBindingConfiguration(Commands.GoToProvider, new[] { Key.G, Key.T }),
new CommandBindingConfiguration(Commands.GoToRoot, new[] { Key.G, Key.R }),
new CommandBindingConfiguration(Commands.HardDelete, new[] { new KeyConfig(Key.D,shift: true), new KeyConfig(Key.D, shift: true) }),
new CommandBindingConfiguration(Commands.Mark, Key.Space),
new CommandBindingConfiguration(Commands.MoveToLast, new KeyConfig(Key.G, shift: true)),
new CommandBindingConfiguration(Commands.MoveToFirst, new[] { Key.G, Key.G }),
new CommandBindingConfiguration(Commands.NextTimelineBlock, Key.L ),
new CommandBindingConfiguration(Commands.NextTimelineCommand, Key.J ),
new CommandBindingConfiguration(Commands.OpenInFileBrowser, new[] { Key.O, Key.E }),
new CommandBindingConfiguration(Commands.PasteMerge, new[] { Key.P, Key.P }),
new CommandBindingConfiguration(Commands.PasteOverwrite, new[] { Key.P, Key.O }),
new CommandBindingConfiguration(Commands.PasteSkip, new[] { Key.P, Key.S }),
new CommandBindingConfiguration(Commands.PreviousTimelineBlock, Key.H ),
new CommandBindingConfiguration(Commands.PreviousTimelineCommand, Key.K ),
new CommandBindingConfiguration(Commands.Refresh, Key.R),
new CommandBindingConfiguration(Commands.Rename, Key.F2),
new CommandBindingConfiguration(Commands.Rename, new[] { Key.C, Key.W }),
new CommandBindingConfiguration(Commands.RunCommand, new KeyConfig(Key.D4, shift: true)),
new CommandBindingConfiguration(Commands.ShowAllShotcut, Key.F1),
new CommandBindingConfiguration(Commands.SoftDelete, new[] { new KeyConfig(Key.D), new KeyConfig(Key.D, shift: true) }),
new CommandBindingConfiguration(Commands.SwitchToLastTab, Key.D9),
new CommandBindingConfiguration(Commands.SwitchToTab1, Key.D1),
new CommandBindingConfiguration(Commands.SwitchToTab2, Key.D2),
new CommandBindingConfiguration(Commands.SwitchToTab3, Key.D3),
new CommandBindingConfiguration(Commands.SwitchToTab4, Key.D4),
new CommandBindingConfiguration(Commands.SwitchToTab5, Key.D5),
new CommandBindingConfiguration(Commands.SwitchToTab6, Key.D6),
new CommandBindingConfiguration(Commands.SwitchToTab7, Key.D7),
new CommandBindingConfiguration(Commands.SwitchToTab8, Key.D8),
new CommandBindingConfiguration(Commands.TimelinePause, new[] { Key.T, Key.P }),
new CommandBindingConfiguration(Commands.TimelineRefresh, new[] { Key.T, Key.R }),
new CommandBindingConfiguration(Commands.TimelineStart, new[] { Key.T, Key.S }),
new CommandBindingConfiguration(Commands.ToggleAdvancedIcons, new[] { Key.Z, Key.I }),
new CommandBindingConfiguration(Commands.GoUp, Key.Left),
new CommandBindingConfiguration(Commands.Open, Key.Right),
new CommandBindingConfiguration(Commands.OpenOrRun, Key.Enter),
new CommandBindingConfiguration(Commands.MoveCursorUp, Key.Up),
new CommandBindingConfiguration(Commands.MoveCursorDown, Key.Down),
new CommandBindingConfiguration(Commands.MoveCursorUpPage, Key.PageUp),
new CommandBindingConfiguration(Commands.MoveCursorDownPage, Key.PageDown),
};
}
} }
} }

View File

@@ -0,0 +1,23 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;
using FileTime.App.Core.Command;
namespace FileTime.Avalonia.Converters
{
public class CommandToCommandNameConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if(value is not Commands command) return value;
//TODO: implement
return command.ToString();
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -1,21 +0,0 @@
using Avalonia.Input;
namespace FileTime.Avalonia.Misc
{
public class KeyWithModifiers
{
public Key Key { get; }
public bool? Alt { get; }
public bool? Shift { get; }
public bool? Ctrl { get; }
public KeyWithModifiers(Key key, bool alt = false, bool shift = false, bool ctrl = false)
{
Key = key;
Alt = alt;
Shift = shift;
Ctrl = ctrl;
}
}
}

View File

@@ -0,0 +1,783 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Threading;
using FileTime.App.Core.Clipboard;
using FileTime.App.Core.Command;
using FileTime.Avalonia.Application;
using FileTime.Avalonia.IconProviders;
using FileTime.Avalonia.Misc;
using FileTime.Avalonia.ViewModels;
using FileTime.Core.Command;
using FileTime.Core.Components;
using FileTime.Core.Interactions;
using FileTime.Core.Models;
using FileTime.Core.Providers;
using FileTime.Core.Timeline;
using FileTime.Providers.Local;
namespace FileTime.Avalonia.Services
{
public class CommandHandlerService
{
private bool _addCommandToNextBatch;
private readonly AppState _appState;
private readonly LocalContentProvider _localContentProvider;
private readonly ItemNameConverterService _itemNameConverterService;
private readonly DialogService _dialogService;
private readonly IClipboard _clipboard;
private readonly TimeRunner _timeRunner;
private readonly IIconProvider _iconProvider;
private readonly IEnumerable<IContentProvider> _contentProviders;
private readonly Dictionary<Commands, Func<Task>> _commandHandlers;
public CommandHandlerService(
AppState appState,
LocalContentProvider localContentProvider,
ItemNameConverterService itemNameConverterService,
DialogService dialogService,
IClipboard clipboard,
TimeRunner timeRunner,
IIconProvider iconProvider,
IEnumerable<IContentProvider> contentProviders)
{
_appState = appState;
_localContentProvider = localContentProvider;
_itemNameConverterService = itemNameConverterService;
_dialogService = dialogService;
_clipboard = clipboard;
_timeRunner = timeRunner;
_iconProvider = iconProvider;
_contentProviders = contentProviders;
_commandHandlers = new Dictionary<Commands, Func<Task>>
{
{Commands.AutoRefresh, ToggleAutoRefresh},
{Commands.ChangeTimelineMode, ChangeTimelineMode},
{Commands.CloseTab, CloseTab},
{Commands.Copy, Copy},
{Commands.CopyPath, CopyPath},
{Commands.CreateContainer, CreateContainer},
{Commands.CreateElement, CreateElement},
{Commands.Cut, Cut},
{Commands.EnterRapidTravel, EnterRapidTravelMode},
{Commands.GoToHome, GotToHome},
{Commands.GoToPath, GoToContainer},
{Commands.GoToProvider, GotToProvider},
{Commands.GoToRoot, GotToRoot},
{Commands.GoUp, GoUp},
{Commands.HardDelete, HardDelete},
{Commands.Mark, MarkCurrentItem},
{Commands.MoveCursorDown, MoveCursorDown},
{Commands.MoveCursorDownPage, MoveCursorDownPage},
{Commands.MoveCursorUp, MoveCursorUp},
{Commands.MoveCursorUpPage, MoveCursorUpPage},
{Commands.MoveToFirst, MoveToFirst},
{Commands.MoveToLast, MoveToLast},
{Commands.NextTimelineBlock, SelectNextTimelineBlock},
{Commands.NextTimelineCommand, SelectNextTimelineCommand},
{Commands.Open, OpenContainer},
{Commands.OpenInFileBrowser, OpenInDefaultFileExplorer},
{Commands.OpenOrRun, OpenOrRun},
{Commands.PasteMerge, PasteMerge},
{Commands.PasteOverwrite, PasteOverwrite},
{Commands.PasteSkip, PasteSkip},
{Commands.PreviousTimelineBlock, SelectPreviousTimelineBlock},
{Commands.PreviousTimelineCommand, SelectPreviousTimelineCommand},
{Commands.Refresh, RefreshCurrentLocation},
{Commands.Rename, Rename},
{Commands.RunCommand, RunCommandInContainer},
{Commands.ShowAllShotcut, ShowAllShortcut},
{Commands.SoftDelete, SoftDelete},
{Commands.SwitchToLastTab, async() => await SwitchToTab(-1)},
{Commands.SwitchToTab1, async() => await SwitchToTab(1)},
{Commands.SwitchToTab2, async() => await SwitchToTab(2)},
{Commands.SwitchToTab3, async() => await SwitchToTab(3)},
{Commands.SwitchToTab4, async() => await SwitchToTab(4)},
{Commands.SwitchToTab5, async() => await SwitchToTab(5)},
{Commands.SwitchToTab6, async() => await SwitchToTab(6)},
{Commands.SwitchToTab7, async() => await SwitchToTab(7)},
{Commands.SwitchToTab8, async() => await SwitchToTab(8)},
{Commands.TimelinePause, PauseTimeline},
{Commands.TimelineRefresh, RefreshTimeline},
{Commands.TimelineStart, ContinueTimeline},
{Commands.ToggleAdvancedIcons, ToggleAdvancedIcons},
{Commands.ToggleHidden, ToggleHidden},
};
}
public async Task HandleCommandAsync(Commands command) =>
await _commandHandlers[command].Invoke();
private async Task OpenContainer()
{
_appState.RapidTravelText = "";
await _appState.SelectedTab.Open();
}
public async Task OpenContainer(IContainer container)
{
_appState.RapidTravelText = "";
await _appState.SelectedTab.OpenContainer(container);
}
private async Task OpenOrRun()
{
if (_appState.SelectedTab.SelectedItem is ContainerViewModel)
{
await OpenContainer();
}
else if (_appState.SelectedTab.SelectedItem is ElementViewModel elementViewModel && elementViewModel.Element is LocalFile localFile)
{
Process.Start(new ProcessStartInfo(localFile.File.FullName) { UseShellExecute = true });
if (_appState.ViewMode == ViewMode.RapidTravel)
{
await _appState.ExitRapidTravelMode();
}
}
}
private async Task GoUp()
{
await _appState.SelectedTab.GoUp();
}
private async Task MoveCursorUp()
{
await _appState.SelectedTab.MoveCursorUp();
}
private async Task MoveCursorDown()
{
await _appState.SelectedTab.MoveCursorDown();
}
private async Task MoveCursorUpPage()
{
await _appState.SelectedTab.MoveCursorUpPage();
}
private async Task MoveCursorDownPage()
{
await _appState.SelectedTab.MoveCursorDownPage();
}
private async Task MoveToFirst()
{
await _appState.SelectedTab.MoveCursorToFirst();
}
private async Task MoveToLast()
{
await _appState.SelectedTab.MoveCursorToLast();
}
private async Task GotToProvider()
{
await _appState.SelectedTab.GotToProvider();
}
private async Task GotToRoot()
{
await _appState.SelectedTab.GotToRoot();
}
private async Task GotToHome()
{
await _appState.SelectedTab.GotToHome();
}
private Task EnterRapidTravelMode()
{
_appState.ViewMode = ViewMode.RapidTravel;
_appState.PreviousKeys.Clear();
_appState.PossibleCommands = new();
return Task.CompletedTask;
}
private async Task SwitchToTab(int number)
{
var tabContainer = _appState.Tabs.FirstOrDefault(t => t.TabNumber == number);
if (number == -1)
{
var greatestNumber = _appState.Tabs.Max(t => t.TabNumber);
tabContainer = _appState.Tabs.FirstOrDefault(t => t.TabNumber == greatestNumber);
}
else if (tabContainer == null)
{
var newContainer = await _appState.SelectedTab.CurrentLocation.Container.Clone();
var newTab = new Tab();
await newTab.Init(newContainer);
tabContainer = new TabContainer(newTab, _localContentProvider, _itemNameConverterService);
await tabContainer.Init(number);
var i = 0;
for (i = 0; i < _appState.Tabs.Count; i++)
{
if (_appState.Tabs[i].TabNumber > number) break;
}
_appState.Tabs.Insert(i, tabContainer);
}
if (_appState.ViewMode == ViewMode.RapidTravel)
{
await _appState.ExitRapidTravelMode();
}
_appState.SelectedTab = tabContainer;
}
private async Task CloseTab()
{
var tabs = _appState.Tabs;
if (tabs.Count > 1)
{
var currentTab = tabs.FirstOrDefault(t => t == _appState.SelectedTab);
if (currentTab != null)
{
tabs.Remove(currentTab);
var tabNumber = tabs[0].TabNumber;
for (var i = 0; i < tabs.Count; i++)
{
tabNumber = tabs[i].TabNumber;
if (tabs[i].TabNumber > currentTab.TabNumber) break;
}
await SwitchToTab(tabNumber);
}
}
}
private Task CreateContainer()
{
var handler = async (List<InputElementWrapper> inputs) =>
{
var container = _appState.SelectedTab.CurrentLocation.Container;
var createContainerCommand = new CreateContainerCommand(new AbsolutePath(container), inputs[0].Value);
await AddCommand(createContainerCommand);
};
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Container name", InputType.Text) }, handler);
return Task.CompletedTask;
}
private Task CreateElement()
{
var handler = async (List<InputElementWrapper> inputs) =>
{
var container = _appState.SelectedTab.CurrentLocation.Container;
var createElementCommand = new CreateElementCommand(new AbsolutePath(container), inputs[0].Value);
await AddCommand(createElementCommand);
};
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Element name", InputType.Text) }, handler);
return Task.CompletedTask;
}
private async Task MarkCurrentItem()
{
await _appState.SelectedTab.MarkCurrentItem();
}
private async Task Copy()
{
_clipboard.Clear();
_clipboard.SetCommand<CopyCommand>();
var currentSelectedItems = await _appState.SelectedTab.TabState.GetCurrentMarkedItems();
if (currentSelectedItems.Count > 0)
{
foreach (var selectedItem in currentSelectedItems)
{
_clipboard.AddContent(selectedItem);
}
await _appState.SelectedTab.TabState.ClearCurrentMarkedItems();
}
else
{
var currentSelectedItem = _appState.SelectedTab.SelectedItem?.Item;
if (currentSelectedItem != null)
{
_clipboard.AddContent(new AbsolutePath(currentSelectedItem));
}
}
}
private Task Cut()
{
_clipboard.Clear();
_clipboard.SetCommand<MoveCommand>();
return Task.CompletedTask;
}
private async Task SoftDelete() => await Delete(false);
private async Task HardDelete() => await Delete(true);
public async Task Delete(bool hardDelete = false)
{
IList<AbsolutePath>? itemsToDelete = null;
var askForDelete = false;
var questionText = "";
var shouldDelete = false;
var shouldClearMarkedItems = false;
var currentSelectedItems = await _appState.SelectedTab.TabState.GetCurrentMarkedItems();
var currentSelectedItem = _appState.SelectedTab.SelectedItem?.Item;
if (currentSelectedItems.Count > 0)
{
itemsToDelete = new List<AbsolutePath>(currentSelectedItems);
shouldClearMarkedItems = true;
//FIXME: check 'is Container'
if (currentSelectedItems.Count == 1)
{
if ((await currentSelectedItems[0].Resolve()) is IContainer container
&& (await container.GetItems())?.Count > 0)
{
askForDelete = true;
questionText = $"The container '{container.Name}' is not empty. Proceed with delete?";
}
else
{
shouldDelete = true;
}
}
else
{
askForDelete = true;
questionText = $"Are you sure you want to delete {itemsToDelete.Count} item?";
}
}
else if (currentSelectedItem != null)
{
itemsToDelete = new List<AbsolutePath>()
{
new AbsolutePath(currentSelectedItem)
};
if (currentSelectedItem is IContainer container && (await container.GetItems())?.Count > 0)
{
askForDelete = true;
questionText = $"The container '{container.Name}' is not empty. Proceed with delete?";
}
else
{
shouldDelete = true;
}
}
if (itemsToDelete?.Count > 0)
{
if (askForDelete)
{
_dialogService.ShowMessageBox(questionText, HandleDelete);
}
else if (shouldDelete)
{
await HandleDelete();
}
}
async Task HandleDelete()
{
var deleteCommand = new DeleteCommand
{
HardDelete = hardDelete
};
foreach (var itemToDelete in itemsToDelete!)
{
deleteCommand.ItemsToDelete.Add(itemToDelete);
}
await AddCommand(deleteCommand);
_clipboard.Clear();
if (shouldClearMarkedItems)
{
await _appState.SelectedTab.TabState.ClearCurrentMarkedItems();
}
}
}
private async Task PasteMerge()
{
await Paste(TransportMode.Merge);
}
private async Task PasteOverwrite()
{
await Paste(TransportMode.Overwrite);
}
private async Task PasteSkip()
{
await Paste(TransportMode.Skip);
}
private async Task Paste(TransportMode transportMode)
{
if (_clipboard.CommandType != null)
{
var command = (ITransportationCommand)Activator.CreateInstance(_clipboard.CommandType!)!;
command.TransportMode = transportMode;
command.Sources.Clear();
foreach (var item in _clipboard.Content)
{
command.Sources.Add(item);
}
var currentLocation = _appState.SelectedTab.CurrentLocation.Container;
command.Target = currentLocation is VirtualContainer virtualContainer
? virtualContainer.BaseContainer
: currentLocation;
await AddCommand(command);
_clipboard.Clear();
}
}
private Task Rename()
{
var selectedItem = _appState.SelectedTab.SelectedItem?.Item;
if (selectedItem != null)
{
var handler = async (List<InputElementWrapper> inputs) =>
{
var renameCommand = new RenameCommand(new AbsolutePath(selectedItem), inputs[0].Value);
await AddCommand(renameCommand);
};
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("New name", InputType.Text, selectedItem.Name) }, handler);
}
return Task.CompletedTask;
}
private async Task RefreshCurrentLocation()
{
await _appState.SelectedTab.CurrentLocation.Container.RefreshAsync();
await _appState.SelectedTab.UpdateCurrentSelectedItem();
}
private Task PauseTimeline()
{
_timeRunner.EnableRunning = false;
return Task.CompletedTask;
}
private async Task ContinueTimeline()
{
_timeRunner.EnableRunning = true;
await _timeRunner.TryStartCommandRunner();
}
private async Task RefreshTimeline()
{
await _timeRunner.Refresh();
}
private Task ChangeTimelineMode()
{
_addCommandToNextBatch = !_addCommandToNextBatch;
_dialogService.ShowToastMessage("Timeline mode: " + (_addCommandToNextBatch ? "Continuous" : "Parallel"));
return Task.CompletedTask;
}
private Task GoToContainer()
{
var handler = async (List<InputElementWrapper> inputs) =>
{
var path = inputs[0].Value;
foreach (var contentProvider in _contentProviders)
{
if (contentProvider.CanHandlePath(path))
{
var possibleContainer = await contentProvider.GetByPath(path);
if (possibleContainer is IContainer container)
{
await _appState.SelectedTab.OpenContainer(container);
}
//TODO: multiple possible content provider handler
return;
}
}
};
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Path", InputType.Text) }, handler);
return Task.CompletedTask;
}
private Task ToggleAdvancedIcons()
{
_iconProvider.EnableAdvancedIcons = !_iconProvider.EnableAdvancedIcons;
_dialogService.ShowToastMessage("Advanced icons are: " + (_iconProvider.EnableAdvancedIcons ? "ON" : "OFF"));
return Task.CompletedTask;
}
private Task ToggleHidden()
{
throw new NotImplementedException();
}
private Task OpenInDefaultFileExplorer()
{
if (_appState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder)
{
var path = localFolder.Directory.FullName;
if (path != null)
{
Process.Start("explorer.exe", "\"" + path + "\"");
}
}
return Task.CompletedTask;
}
private async Task CopyPath()
{
string? textToCopy = null;
if (_appState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder)
{
textToCopy = localFolder.Directory.FullName;
}
if (_appState.SelectedTab.CurrentLocation.Container is LocalFile localFile)
{
textToCopy = localFile.File.FullName;
}
else if (_appState.SelectedTab.CurrentLocation.Container.FullName is string fullName)
{
textToCopy = fullName;
}
if (textToCopy != null && global::Avalonia.Application.Current?.Clipboard is not null)
{
await global::Avalonia.Application.Current.Clipboard.SetTextAsync(textToCopy);
}
}
private Task ShowAllShortcut()
{
_appState.IsAllShortcutVisible = true;
return Task.CompletedTask;
}
private Task RunCommandInContainer()
{
var handler = (List<InputElementWrapper> inputs) =>
{
var input = inputs[0].Value;
string? path = null;
string? arguments = null;
if (input.StartsWith("\""))
{
var pathEnd = input.IndexOf('\"', 1);
path = input.Substring(1, pathEnd);
arguments = input.Substring(pathEnd + 1).Trim();
}
else
{
var inputParts = input.Split(' ');
path = inputParts[0];
arguments = inputParts.Length > 1 ? string.Join(' ', inputParts[1..]).Trim() : null;
}
if (!string.IsNullOrWhiteSpace(path))
{
using var process = new Process();
process.StartInfo.FileName = path;
if (!string.IsNullOrWhiteSpace(arguments))
{
process.StartInfo.Arguments = arguments;
}
if (_appState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder)
{
process.StartInfo.WorkingDirectory = localFolder.Directory.FullName;
}
process.Start();
}
return Task.CompletedTask;
};
_dialogService.ReadInputs(new List<InputElement>() { new InputElement("Command", InputType.Text) }, handler);
return Task.CompletedTask;
}
private Task SelectPreviousTimelineBlock()
{
var currentSelected = GetSelectedTimelineCommandOrSelectFirst();
if (currentSelected == null) return Task.CompletedTask;
ParallelCommandsViewModel? newBlockVM = null;
ParallelCommandsViewModel? previousBlockVM = null;
foreach (var timelineBlock in _appState.TimelineCommands)
{
foreach (var command in timelineBlock.ParallelCommands)
{
if (command.IsSelected)
{
newBlockVM = previousBlockVM;
break;
}
}
previousBlockVM = timelineBlock;
}
if (newBlockVM == null) return Task.CompletedTask;
foreach (var val in _appState.TimelineCommands.Select(t => t.ParallelCommands.Select((c, i) => (ParalellCommandVM: t, CommandVM: c, Index: i))).SelectMany(t => t))
{
val.CommandVM.IsSelected = val.ParalellCommandVM == newBlockVM && val.Index == 0;
}
return Task.CompletedTask;
}
private Task SelectNextTimelineCommand()
{
var currentSelected = GetSelectedTimelineCommandOrSelectFirst();
if (currentSelected == null) return Task.CompletedTask;
ParallelCommandViewModel? lastCommand = null;
var any = false;
foreach (var command in _appState.TimelineCommands.SelectMany(t => t.ParallelCommands))
{
var isSelected = lastCommand == currentSelected;
command.IsSelected = isSelected;
any = any || isSelected;
lastCommand = command;
}
if (!any && lastCommand != null) lastCommand.IsSelected = true;
return Task.CompletedTask;
}
private Task SelectPreviousTimelineCommand()
{
var currentSelected = GetSelectedTimelineCommandOrSelectFirst();
if (currentSelected == null) return Task.CompletedTask;
ParallelCommandViewModel? lastCommand = null;
foreach (var command in _appState.TimelineCommands.SelectMany(t => t.ParallelCommands))
{
if (lastCommand != null)
{
lastCommand.IsSelected = command == currentSelected;
}
lastCommand = command;
}
if (lastCommand != null) lastCommand.IsSelected = false;
return Task.CompletedTask;
}
private Task SelectNextTimelineBlock()
{
var currentSelected = GetSelectedTimelineCommandOrSelectFirst();
if (currentSelected == null) return Task.CompletedTask;
ParallelCommandsViewModel? newBlockVM = null;
var select = false;
foreach (var timelineBlock in _appState.TimelineCommands)
{
if (select)
{
newBlockVM = timelineBlock;
break;
}
foreach (var command in timelineBlock.ParallelCommands)
{
if (command.IsSelected)
{
select = true;
break;
}
}
}
if (newBlockVM == null) return Task.CompletedTask;
foreach (var val in _appState.TimelineCommands.Select(t => t.ParallelCommands.Select((c, i) => (ParalellCommandVM: t, CommandVM: c, Index: i))).SelectMany(t => t))
{
val.CommandVM.IsSelected = val.ParalellCommandVM == newBlockVM && val.Index == 0;
}
return Task.CompletedTask;
}
private ParallelCommandViewModel? GetSelectedTimelineCommandOrSelectFirst()
{
var currentSelected = _appState.TimelineCommands.SelectMany(t => t.ParallelCommands).FirstOrDefault(c => c.IsSelected);
if (currentSelected != null) return currentSelected;
var firstCommand = _appState.TimelineCommands.SelectMany(t => t.ParallelCommands).FirstOrDefault();
if (firstCommand != null)
{
firstCommand.IsSelected = true;
}
return null;
}
private async Task AddCommand(ICommand command)
{
if (_addCommandToNextBatch)
{
await _timeRunner.AddCommand(command, toNewBatch: true);
}
else
{
ParallelCommandsViewModel? batchToAdd = null;
foreach (var val in _appState.TimelineCommands.Select(t => t.ParallelCommands.Select(c => (ParalellCommandVM: t, CommandVM: c))).SelectMany(t => t))
{
if (val.CommandVM.IsSelected)
{
batchToAdd = val.ParalellCommandVM;
break;
}
}
if (batchToAdd != null)
{
await _timeRunner.AddCommand(command, batchToAdd.Id);
}
else
{
await _timeRunner.AddCommand(command);
}
}
}
private Task ToggleAutoRefresh()
{
var tab = _appState.SelectedTab.TabState.Tab;
tab.AutoRefresh = !tab.AutoRefresh;
_dialogService.ShowToastMessage("Auto refresh is: " + (tab.AutoRefresh ? "ON" : "OFF"));
return Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Threading;
using FileTime.Avalonia.Application;
using FileTime.Avalonia.Misc;
using FileTime.Core.Interactions;
namespace FileTime.Avalonia.Services
{
public class DialogService
{
private readonly AppState _appState;
private Func<List<InputElementWrapper>, Task>? _inputHandler;
public DialogService(AppState appState)
{
_appState = appState;
}
public void ReadInputs(List<InputElement> inputs, Action<List<InputElementWrapper>> inputHandler) =>
ReadInputs(inputs, (inputs) => { inputHandler(inputs); return Task.CompletedTask; });
public void ReadInputs(List<InputElement> inputs, Func<List<InputElementWrapper>, Task> inputHandler)
{
_appState.Inputs = inputs.ConvertAll(i => new InputElementWrapper(i, i.DefaultValue));
_inputHandler = inputHandler;
}
public async Task<string?[]> ReadInputs(IEnumerable<InputElement> fields)
{
var waiting = true;
var result = Array.Empty<string>();
ReadInputs(fields.ToList(), (inputs) =>
{
if (inputs != null)
{
result = inputs.Select(i => i.Value).ToArray();
}
waiting = false;
});
while (waiting) await Task.Delay(100);
return result;
}
public void ClearInputs()
{
_appState.Inputs = null;
_inputHandler = null;
}
public async Task ProcessInputs()
{
try
{
if (_inputHandler != null)
{
await _inputHandler.Invoke(_appState.Inputs);
}
}
catch { }
ClearInputs();
}
public void CancelInputs()
{
ClearInputs();
}
public void ShowMessageBox(string text, Func<Task> inputHandler)
{
_appState.MessageBoxText = text;
_inputHandler = async (_) => await inputHandler();
}
public void ProcessMessageBox()
{
_inputHandler?.Invoke(null!);
_appState.MessageBoxText = null;
_inputHandler = null;
}
public void CancelMessageBox()
{
_appState.MessageBoxText = null;
_inputHandler = null;
}
public void ShowToastMessage(string text)
{
_appState.PopupTexts.Add(text);
Task.Run(async () =>
{
await Task.Delay(5000);
await Dispatcher.UIThread.InvokeAsync(() => _appState.PopupTexts.Remove(text));
});
}
}
}

View File

@@ -0,0 +1,215 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Input;
using FileTime.Avalonia.Application;
using FileTime.Avalonia.Configuration;
using FileTime.Avalonia.ViewModels;
using FileTime.Core.Extensions;
using FileTime.Core.Models;
namespace FileTime.Avalonia.Services
{
public class KeyInputHandlerService
{
private readonly List<KeyConfig[]> _keysToSkip = new();
private readonly AppState _appState;
private readonly KeyboardConfigurationService _keyboardConfigurationService;
private readonly CommandHandlerService _commandHandlerService;
private readonly DialogService _dialogService;
public KeyInputHandlerService(
AppState appState,
KeyboardConfigurationService keyboardConfigurationService,
CommandHandlerService commandHandlerService,
DialogService dialogService)
{
_appState = appState;
_keyboardConfigurationService = keyboardConfigurationService;
_commandHandlerService = commandHandlerService;
_dialogService = dialogService;
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Up) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Down) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.Tab) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.PageDown) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.PageUp) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.F4, alt: true) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.LWin) });
_keysToSkip.Add(new KeyConfig[] { new KeyConfig(Key.RWin) });
}
public async Task ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled)
{
if (key == Key.LeftAlt
|| key == Key.RightAlt
|| key == Key.LeftShift
|| key == Key.RightShift
|| key == Key.LeftCtrl
|| key == Key.RightCtrl) return;
_appState.NoCommandFound = false;
var isAltPressed = (keyModifiers & KeyModifiers.Alt) == KeyModifiers.Alt;
var isShiftPressed = (keyModifiers & KeyModifiers.Shift) == KeyModifiers.Shift;
var isCtrlPressed = (keyModifiers & KeyModifiers.Control) == KeyModifiers.Control;
if (_appState.ViewMode == ViewMode.Default)
{
var keyWithModifiers = new KeyConfig(key, shift: isShiftPressed, alt: isAltPressed, ctrl: isCtrlPressed);
_appState.PreviousKeys.Add(keyWithModifiers);
var selectedCommandBinding = _keyboardConfigurationService.UniversalCommandBindings.FirstOrDefault(c => AreKeysEqual(c.Keys, _appState.PreviousKeys));
selectedCommandBinding ??= _keyboardConfigurationService.CommandBindings.FirstOrDefault(c => AreKeysEqual(c.Keys, _appState.PreviousKeys));
if (key == Key.Escape)
{
_appState.IsAllShortcutVisible = false;
_appState.MessageBoxText = null;
_appState.PreviousKeys.Clear();
_appState.PossibleCommands = new();
setHandled(true);
}
else if (key == Key.Enter
&& _appState.MessageBoxText != null)
{
_appState.PreviousKeys.Clear();
_dialogService.ProcessMessageBox();
setHandled(true);
}
else if (selectedCommandBinding != null)
{
setHandled(true);
await _commandHandlerService.HandleCommandAsync(selectedCommandBinding.Command);
_appState.PreviousKeys.Clear();
_appState.PossibleCommands = new();
}
else if (_keysToSkip.Any(k => AreKeysEqual(k, _appState.PreviousKeys)))
{
_appState.PreviousKeys.Clear();
_appState.PossibleCommands = new();
return;
}
else if (_appState.PreviousKeys.Count == 2)
{
setHandled(true);
_appState.NoCommandFound = true;
_appState.PreviousKeys.Clear();
_appState.PossibleCommands = new();
}
else
{
var possibleCommands = _keyboardConfigurationService.AllShortcut.Where(c => AreKeysEqual(c.Keys[0], keyWithModifiers)).ToList();
if (possibleCommands.Count == 0)
{
_appState.NoCommandFound = true;
_appState.PreviousKeys.Clear();
}
else
{
_appState.PossibleCommands = possibleCommands;
}
setHandled(true);
}
}
else
{
var keyString = key.ToString();
var updateRapidTravelFilter = false;
if (key == Key.Escape)
{
setHandled(true);
if (_appState.IsAllShortcutVisible)
{
_appState.IsAllShortcutVisible = false;
}
else if (_appState.MessageBoxText != null)
{
_appState.MessageBoxText = null;
}
else
{
await _appState.ExitRapidTravelMode();
}
}
else if (key == Key.Back)
{
if (_appState.RapidTravelText.Length > 0)
{
setHandled(true);
_appState.RapidTravelText = _appState.RapidTravelText.Substring(0, _appState.RapidTravelText.Length - 1);
updateRapidTravelFilter = true;
}
}
else if (keyString.Length == 1)
{
setHandled(true);
_appState.RapidTravelText += keyString.ToLower();
updateRapidTravelFilter = true;
}
else
{
var currentKeyAsList = new List<KeyConfig>() { new KeyConfig(key) };
var selectedCommandBinding = _keyboardConfigurationService.UniversalCommandBindings.FirstOrDefault(c => AreKeysEqual(c.Keys, currentKeyAsList));
if (selectedCommandBinding != null)
{
setHandled(true);
await _commandHandlerService.HandleCommandAsync(selectedCommandBinding.Command);
}
}
if (updateRapidTravelFilter)
{
var currentLocation = await _appState.SelectedTab.CurrentLocation.Container.WithoutVirtualContainer(MainPageViewModel.RAPIDTRAVEL);
var newLocation = new VirtualContainer(
currentLocation,
new List<Func<IEnumerable<IContainer>, IEnumerable<IContainer>>>()
{
container => container.Where(c => c.Name.ToLower().Contains(_appState.RapidTravelText))
},
new List<Func<IEnumerable<IElement>, IEnumerable<IElement>>>()
{
element => element.Where(e => e.Name.ToLower().Contains(_appState.RapidTravelText))
},
virtualContainerName: MainPageViewModel.RAPIDTRAVEL
);
await newLocation.Init();
await _appState.SelectedTab.OpenContainer(newLocation);
var selectedItemName = _appState.SelectedTab.SelectedItem?.Item.Name;
var currentLocationItems = await _appState.SelectedTab.CurrentLocation.GetItems();
if (currentLocationItems.FirstOrDefault(i => string.Equals(i.Item.Name, _appState.RapidTravelText, StringComparison.OrdinalIgnoreCase)) is IItemViewModel matchItem)
{
await _appState.SelectedTab.SetCurrentSelectedItem(matchItem.Item);
}
else if (!currentLocationItems.Select(i => i.Item.Name).Any(n => n == selectedItemName))
{
await _appState.SelectedTab.MoveCursorToFirst();
}
}
}
}
private static bool AreKeysEqual(IReadOnlyList<KeyConfig> collection1, IReadOnlyList<KeyConfig> collection2)
{
if (collection1.Count != collection2.Count) return false;
for (var i = 0; i < collection1.Count; i++)
{
if (!AreKeysEqual(collection1[i], collection2[i])) return false;
}
return true;
}
private static bool AreKeysEqual(KeyConfig key1, KeyConfig key2) =>
key1.Key == key2.Key
&& key1.Alt == key2.Alt
&& key1.Shift == key2.Shift
&& key1.Ctrl == key2.Ctrl;
}
}

View File

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

View File

@@ -27,6 +27,10 @@ namespace FileTime.Avalonia
serviceCollection = serviceCollection serviceCollection = serviceCollection
.AddSingleton<ItemNameConverterService>() .AddSingleton<ItemNameConverterService>()
.AddSingleton<StatePersistenceService>() .AddSingleton<StatePersistenceService>()
.AddSingleton<CommandHandlerService>()
.AddSingleton<KeyboardConfigurationService>()
.AddSingleton<KeyInputHandlerService>()
.AddSingleton<DialogService>()
.AddSingleton<IIconProvider, MaterialIconProvider>(); .AddSingleton<IIconProvider, MaterialIconProvider>();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
@@ -66,18 +70,19 @@ namespace FileTime.Avalonia
.Build(); .Build();
return serviceCollection return serviceCollection
//.Configure<SzopiAPIConfig>(configuration.GetSection("server")) .Configure<KeyBindingConfiguration>(configuration.GetSection(MainConfiguration.KeybindingBaseConfigKey))
.AddSingleton<IConfiguration>(configuration); .AddSingleton<IConfiguration>(configuration);
} }
internal static IServiceCollection InitSerilog(this IServiceCollection serviceCollection) internal static IServiceCollection InitSerilog(this IServiceCollection serviceCollection)
{ {
using var serviceProvider = serviceCollection.BuildServiceProvider();
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(serviceCollection.BuildServiceProvider().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)
.CreateLogger(); .CreateLogger();

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,8 @@
ExtendClientAreaToDecorationsHint="True" ExtendClientAreaToDecorationsHint="True"
Opened="OnWindowOpened" Opened="OnWindowOpened"
Closed="OnWindowClosed" Closed="OnWindowClosed"
x:DataType="vm:MainPageViewModel"
x:CompileBindings="True"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid Background="{DynamicResource AppBackgroundBrush}"> <Grid Background="{DynamicResource AppBackgroundBrush}">
@@ -149,7 +151,7 @@
<Grid Grid.Column="1" Grid.Row="1" RowDefinitions="Auto,40,*,Auto"> <Grid Grid.Column="1" Grid.Row="1" RowDefinitions="Auto,40,*,Auto">
<Grid> <Grid>
<ItemsControl Items="{Binding TimelineCommands}"> <ItemsControl Items="{Binding AppState.TimelineCommands}">
<ItemsControl.ItemsPanel> <ItemsControl.ItemsPanel>
<ItemsPanelTemplate> <ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" /> <StackPanel Orientation="Horizontal" />
@@ -335,7 +337,7 @@
</Grid> </Grid>
</Grid> </Grid>
<ItemsRepeater Items="{Binding PopupTexts}" Margin="0,0,0,20" HorizontalAlignment="Center" VerticalAlignment="Bottom" IsVisible="{Binding PopupTexts.Count,Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}"> <ItemsRepeater Items="{Binding AppState.PopupTexts}" Margin="0,0,0,20" HorizontalAlignment="Center" VerticalAlignment="Bottom" IsVisible="{Binding AppState.PopupTexts.Count,Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
<ItemsRepeater.Styles> <ItemsRepeater.Styles>
<Style Selector="TextBlock"> <Style Selector="TextBlock">
<Style.Animations> <Style.Animations>
@@ -387,7 +389,7 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
<Grid IsVisible="{Binding NoCommandFound}"> <Grid IsVisible="{Binding AppState.NoCommandFound}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="1" /> <RowDefinition Height="1" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@@ -408,7 +410,7 @@
Text="No command found" /> Text="No command found" />
</Grid> </Grid>
<Grid IsVisible="{Binding PossibleCommands.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}"> <Grid IsVisible="{Binding AppState.PossibleCommands.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="1" /> <RowDefinition Height="1" />
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
@@ -423,7 +425,7 @@
<ItemsRepeater <ItemsRepeater
Grid.Row="1" Grid.Row="1"
Items="{Binding PossibleCommands}"> Items="{Binding AppState.PossibleCommands}">
<ItemsRepeater.ItemTemplate> <ItemsRepeater.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid> <Grid>
@@ -433,7 +435,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Text="{Binding KeysDisplayText}" /> <TextBlock Text="{Binding KeysDisplayText}" />
<TextBlock Grid.Column="1" Text="{Binding Name}" /> <TextBlock Grid.Column="1" Text="{Binding Command, Converter={StaticResource CommandToCommandNameConverter}}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ItemsRepeater.ItemTemplate> </ItemsRepeater.ItemTemplate>
@@ -447,7 +449,7 @@
Background="{DynamicResource BarelyTransparentBackgroundColor}" Background="{DynamicResource BarelyTransparentBackgroundColor}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{Binding Inputs, Converter={StaticResource IsNotNullConverter}}"> IsVisible="{Binding AppState.Inputs, Converter={StaticResource IsNotNullConverter}}">
<Border <Border
Background="{DynamicResource ContainerBackgroundBrush}" Background="{DynamicResource ContainerBackgroundBrush}"
Padding="20" Padding="20"
@@ -457,7 +459,7 @@
<ItemsControl <ItemsControl
x:Name="InputList" x:Name="InputList"
Items="{Binding Inputs}"> Items="{Binding AppState.Inputs}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid MinWidth="500" ColumnDefinitions="250,*" Margin="10,5"> <Grid MinWidth="500" ColumnDefinitions="250,*" Margin="10,5">
@@ -502,7 +504,7 @@
Background="{DynamicResource BarelyTransparentBackgroundColor}" Background="{DynamicResource BarelyTransparentBackgroundColor}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
IsVisible="{Binding MessageBoxText, Converter={StaticResource IsNotNullConverter}}"> IsVisible="{Binding AppState.MessageBoxText, Converter={StaticResource IsNotNullConverter}}">
<Border <Border
Background="{DynamicResource ContainerBackgroundBrush}" Background="{DynamicResource ContainerBackgroundBrush}"
HorizontalAlignment="Center" HorizontalAlignment="Center"
@@ -510,7 +512,7 @@
Padding="20"> Padding="20">
<Grid RowDefinitions="Auto,Auto"> <Grid RowDefinitions="Auto,Auto">
<TextBlock Text="{Binding MessageBoxText}"/> <TextBlock Text="{Binding AppState.MessageBoxText}"/>
<StackPanel <StackPanel
Grid.Row="1" Grid.Row="1"
Orientation="Horizontal" Orientation="Horizontal"
@@ -535,14 +537,14 @@
Background="{DynamicResource TransparentContainerBackgroundBrush}" Background="{DynamicResource TransparentContainerBackgroundBrush}"
Margin="20" Margin="20"
HorizontalAlignment="Center" HorizontalAlignment="Center"
IsVisible="{Binding IsAllShortcutVisible}"> IsVisible="{Binding AppState.IsAllShortcutVisible}">
<Grid RowDefinitions="Auto, *" Margin="30,10"> <Grid RowDefinitions="Auto, *" Margin="30,10">
<TextBlock Text="Shortcuts" Margin="0,0,0,20"/> <TextBlock Text="Shortcuts" Margin="0,0,0,20"/>
<ScrollViewer <ScrollViewer
Grid.Row="1" Grid.Row="1"
HorizontalScrollBarVisibility="Disabled"> HorizontalScrollBarVisibility="Disabled">
<ItemsControl <ItemsControl
Items="{Binding AllShortcut}"> Items="{Binding KeyboardConfigurationService.AllShortcut}">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid Margin="0,5,10,5"> <Grid Margin="0,5,10,5">
@@ -552,7 +554,7 @@
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock Text="{Binding KeysDisplayText}" /> <TextBlock Text="{Binding KeysDisplayText}" />
<TextBlock Grid.Column="1" Text="{Binding Name}" /> <TextBlock Grid.Column="1" Text="{Binding Command, Converter={StaticResource CommandToCommandNameConverter}}" />
</Grid> </Grid>
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>

View File

@@ -55,13 +55,13 @@ namespace FileTime.Avalonia.Views
private void InputText_KeyDown(object sender, KeyEventArgs e) private void InputText_KeyDown(object sender, KeyEventArgs e)
{ {
if (e.Key == Key.Enter && _inputElementWrapper == ViewModel!.Inputs.Last()) if (e.Key == Key.Enter && _inputElementWrapper == ViewModel!.AppState.Inputs.Last())
{ {
ViewModel.ProcessInputs(); ViewModel.ProcessInputs();
_inputElementWrapper = null; _inputElementWrapper = null;
e.Handled = true; e.Handled = true;
} }
else if (e.Key == Key.Escape && _inputElementWrapper == ViewModel!.Inputs.Last()) else if (e.Key == Key.Escape && _inputElementWrapper == ViewModel!.AppState.Inputs.Last())
{ {
ViewModel.CancelInputs(); ViewModel.CancelInputs();
_inputElementWrapper = null; _inputElementWrapper = null;
@@ -87,7 +87,7 @@ namespace FileTime.Avalonia.Views
private void InputText_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e) private void InputText_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e)
{ {
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper && inputElementWrapper == ViewModel!.Inputs.First()) if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper && inputElementWrapper == ViewModel!.AppState.Inputs.First())
{ {
inputText.Focus(); inputText.Focus();
} }
@@ -101,7 +101,7 @@ namespace FileTime.Avalonia.Views
&& sender is StyledElement control && sender is StyledElement control
&& control.DataContext is PlaceInfo placeInfo) && control.DataContext is PlaceInfo placeInfo)
{ {
ViewModel.OpenContainer(placeInfo.Container); ViewModel.CommandHandlerService.OpenContainer(placeInfo.Container);
e.Handled = true; e.Handled = true;
} }
} }
@@ -114,7 +114,7 @@ namespace FileTime.Avalonia.Views
&& sender is StyledElement control && sender is StyledElement control
&& control.DataContext is RootDriveInfo rootDriveInfo) && control.DataContext is RootDriveInfo rootDriveInfo)
{ {
ViewModel.OpenContainer(rootDriveInfo.Container); ViewModel.CommandHandlerService.OpenContainer(rootDriveInfo.Container);
e.Handled = true; e.Handled = true;
} }
} }