MainPageViewModel refactor, keybindings from config
This commit is contained in:
783
src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs
Normal file
783
src/GuiApp/FileTime.Avalonia/Services/CommandHandlerService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
src/GuiApp/FileTime.Avalonia/Services/DialogService.cs
Normal file
105
src/GuiApp/FileTime.Avalonia/Services/DialogService.cs
Normal 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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
215
src/GuiApp/FileTime.Avalonia/Services/KeyInputHandlerService.cs
Normal file
215
src/GuiApp/FileTime.Avalonia/Services/KeyInputHandlerService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user