TimeTravel
This commit is contained in:
@@ -1,35 +1,36 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.App.Core.Clipboard
|
||||
{
|
||||
public class Clipboard : IClipboard
|
||||
{
|
||||
private readonly List<ClipboardItem> _content;
|
||||
public IReadOnlyList<ClipboardItem> Content { get; }
|
||||
private readonly List<AbsolutePath> _content;
|
||||
public IReadOnlyList<AbsolutePath> Content { get; }
|
||||
public Type? CommandType { get; private set; }
|
||||
|
||||
public Clipboard()
|
||||
{
|
||||
_content = new List<ClipboardItem>();
|
||||
_content = new List<AbsolutePath>();
|
||||
Content = _content.AsReadOnly();
|
||||
}
|
||||
|
||||
public void AddContent(IContentProvider contentProvider, string path)
|
||||
public void AddContent(AbsolutePath absolutePath)
|
||||
{
|
||||
foreach (var content in _content)
|
||||
{
|
||||
if (content.ContentProvider == contentProvider && content.Path == path) return;
|
||||
if (content.IsEqual(absolutePath)) return;
|
||||
}
|
||||
|
||||
_content.Add(new ClipboardItem(contentProvider, path));
|
||||
_content.Add(new AbsolutePath(absolutePath));
|
||||
}
|
||||
|
||||
public void RemoveContent(IContentProvider contentProvider, string path)
|
||||
public void RemoveContent(AbsolutePath absolutePath)
|
||||
{
|
||||
for (var i = 0; i < _content.Count; i++)
|
||||
{
|
||||
if (_content[i].ContentProvider == contentProvider && _content[i].Path == path)
|
||||
if (_content[i].IsEqual(absolutePath))
|
||||
{
|
||||
_content.RemoveAt(i--);
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.App.Core.Clipboard
|
||||
{
|
||||
public class ClipboardItem : IAbsolutePath
|
||||
{
|
||||
public IContentProvider ContentProvider { get; }
|
||||
public string Path { get; }
|
||||
|
||||
public ClipboardItem(IContentProvider contentProvider, string path)
|
||||
{
|
||||
ContentProvider = contentProvider;
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.App.Core.Clipboard
|
||||
{
|
||||
public interface IClipboard
|
||||
{
|
||||
IReadOnlyList<ClipboardItem> Content { get; }
|
||||
IReadOnlyList<AbsolutePath> Content { get; }
|
||||
Type? CommandType { get; }
|
||||
|
||||
void AddContent(IContentProvider contentProvider, string path);
|
||||
void AddContent(AbsolutePath absolutePath);
|
||||
void Clear();
|
||||
void RemoveContent(IContentProvider contentProvider, string path);
|
||||
void RemoveContent(AbsolutePath absolutePath);
|
||||
void SetCommand<T>() where T : ITransportationCommand;
|
||||
}
|
||||
}
|
||||
@@ -4,26 +4,32 @@ namespace FileTime.App.Core.Command
|
||||
{
|
||||
CloseTab,
|
||||
Copy,
|
||||
Cut,
|
||||
GoUp,
|
||||
MoveCursorDown,
|
||||
MoveCursorUp,
|
||||
Open,
|
||||
Paste,
|
||||
Select,
|
||||
ToggleHidden,
|
||||
CreateContainer,
|
||||
CreateElement,
|
||||
MoveCursorUpPage,
|
||||
Cut,
|
||||
EnterRapidTravel,
|
||||
GoToHome,
|
||||
GoToProvider,
|
||||
GoToRoot,
|
||||
GoUp,
|
||||
Delete,
|
||||
MoveCursorDown,
|
||||
MoveCursorDownPage,
|
||||
MoveToTop,
|
||||
MoveCursorUp,
|
||||
MoveCursorUpPage,
|
||||
MoveToBottom,
|
||||
MoveToFirst,
|
||||
MoveToLast,
|
||||
GoToRoot,
|
||||
GoToProvider,
|
||||
GoToHome,
|
||||
EnterRapidTravel,
|
||||
MoveToTop,
|
||||
Open,
|
||||
OpenOrRun,
|
||||
PasteMerge,
|
||||
PasteOverwrite,
|
||||
PasteSkip,
|
||||
Select,
|
||||
ToggleHidden,
|
||||
Rename,
|
||||
Dummy,
|
||||
Refresh,
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.App.Core.Tab
|
||||
{
|
||||
public class TabItem : IAbsolutePath
|
||||
{
|
||||
public IContentProvider ContentProvider { get; }
|
||||
public string Path { get; }
|
||||
|
||||
public TabItem(IContentProvider contentProvider, string path)
|
||||
{
|
||||
ContentProvider = contentProvider;
|
||||
Path = path;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +1,108 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.App.Core.Tab
|
||||
{
|
||||
public class TabState
|
||||
{
|
||||
private readonly Dictionary<IContainer, List<TabItem>> _selectedItems;
|
||||
private readonly Dictionary<IContainer, IReadOnlyList<TabItem>> _selectedItemsReadOnly;
|
||||
public IReadOnlyDictionary<IContainer, IReadOnlyList<TabItem>> SelectedItems { get; }
|
||||
private readonly Dictionary<IContainer, List<AbsolutePath>> _markedItems;
|
||||
private readonly Dictionary<IContainer, IReadOnlyList<AbsolutePath>> _markedItemsReadOnly;
|
||||
public IReadOnlyDictionary<IContainer, IReadOnlyList<AbsolutePath>> MarkedItems { get; }
|
||||
|
||||
public FileTime.Core.Components.Tab Tab { get; }
|
||||
|
||||
public AsyncEventHandler<TabState, AbsolutePath> ItemMarked { get; } = new();
|
||||
public AsyncEventHandler<TabState, AbsolutePath> ItemUnmarked { get; } = new();
|
||||
|
||||
public TabState(FileTime.Core.Components.Tab pane)
|
||||
{
|
||||
Tab = pane;
|
||||
|
||||
_selectedItems = new Dictionary<IContainer, List<TabItem>>();
|
||||
_selectedItemsReadOnly = new Dictionary<IContainer, IReadOnlyList<TabItem>>();
|
||||
SelectedItems = new ReadOnlyDictionary<IContainer, IReadOnlyList<TabItem>>(_selectedItemsReadOnly);
|
||||
_markedItems = new Dictionary<IContainer, List<AbsolutePath>>();
|
||||
_markedItemsReadOnly = new Dictionary<IContainer, IReadOnlyList<AbsolutePath>>();
|
||||
MarkedItems = new ReadOnlyDictionary<IContainer, IReadOnlyList<AbsolutePath>>(_markedItemsReadOnly);
|
||||
}
|
||||
|
||||
public void AddSelectedItem(IContentProvider contentProvider, IContainer container, string path)
|
||||
public async Task AddMarkedItem(IContainer container, AbsolutePath path)
|
||||
{
|
||||
if (!_selectedItems.ContainsKey(container))
|
||||
if (!_markedItems.ContainsKey(container))
|
||||
{
|
||||
var val = new List<TabItem>();
|
||||
_selectedItems.Add(container, val);
|
||||
_selectedItemsReadOnly.Add(container, val.AsReadOnly());
|
||||
var val = new List<AbsolutePath>();
|
||||
_markedItems.Add(container, val);
|
||||
_markedItemsReadOnly.Add(container, val.AsReadOnly());
|
||||
}
|
||||
|
||||
foreach (var content in _selectedItems[container])
|
||||
foreach (var content in _markedItems[container])
|
||||
{
|
||||
if (content.ContentProvider == contentProvider && content.Path == path) return;
|
||||
if (content.IsEqual(path)) return;
|
||||
}
|
||||
|
||||
_selectedItems[container].Add(new TabItem(contentProvider, path));
|
||||
var tabItem = new AbsolutePath(path);
|
||||
_markedItems[container].Add(tabItem);
|
||||
await ItemMarked.InvokeAsync(this, tabItem);
|
||||
}
|
||||
|
||||
public void RemoveSelectedItem(IContentProvider contentProvider, IContainer container, string path)
|
||||
public async Task RemoveMarkedItem(IContainer container, AbsolutePath path)
|
||||
{
|
||||
if (_selectedItems.ContainsKey(container))
|
||||
if (_markedItems.ContainsKey(container))
|
||||
{
|
||||
var selectedItems = _selectedItems[container];
|
||||
for (var i = 0; i < selectedItems.Count; i++)
|
||||
var markedItems = _markedItems[container];
|
||||
for (var i = 0; i < markedItems.Count; i++)
|
||||
{
|
||||
if (selectedItems[i].ContentProvider == contentProvider && selectedItems[i].Path == path)
|
||||
if (markedItems[i].IsEqual(path))
|
||||
{
|
||||
selectedItems.RemoveAt(i--);
|
||||
await ItemUnmarked.InvokeAsync(this, markedItems[i]);
|
||||
markedItems.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ContainsSelectedItem(IContentProvider contentProvider, IContainer container, string path)
|
||||
public bool ContainsMarkedItem(IContainer container, AbsolutePath path)
|
||||
{
|
||||
if (!_selectedItems.ContainsKey(container)) return false;
|
||||
if (!_markedItems.ContainsKey(container)) return false;
|
||||
|
||||
foreach (var content in _selectedItems[container])
|
||||
foreach (var content in _markedItems[container])
|
||||
{
|
||||
if (content.ContentProvider == contentProvider && content.Path == path) return true;
|
||||
if (content.Equals(path)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<TabItem>> GetCurrentSelectedItems()
|
||||
public async Task<IReadOnlyList<AbsolutePath>> GetCurrentMarkedItems()
|
||||
{
|
||||
var currentLocation = await Tab.GetCurrentLocation();
|
||||
return GetCurrentMarkedItems(await Tab.GetCurrentLocation());
|
||||
}
|
||||
|
||||
return SelectedItems.ContainsKey(currentLocation)
|
||||
? SelectedItems[currentLocation]
|
||||
: new List<TabItem>().AsReadOnly();
|
||||
public IReadOnlyList<AbsolutePath> GetCurrentMarkedItems(IContainer container)
|
||||
{
|
||||
return MarkedItems.ContainsKey(container)
|
||||
? MarkedItems[container]
|
||||
: new List<AbsolutePath>().AsReadOnly();
|
||||
}
|
||||
|
||||
public async Task MakrCurrentItem()
|
||||
{
|
||||
var currentLocation = await Tab!.GetCurrentLocation();
|
||||
if (currentLocation != null)
|
||||
{
|
||||
var currentSelectedItem = await Tab.GetCurrentSelectedItem()!;
|
||||
if (currentSelectedItem != null)
|
||||
{
|
||||
if (ContainsMarkedItem(currentLocation, new AbsolutePath(currentSelectedItem)))
|
||||
{
|
||||
await RemoveMarkedItem(currentLocation, new AbsolutePath(currentSelectedItem));
|
||||
}
|
||||
else
|
||||
{
|
||||
await AddMarkedItem(currentLocation, new AbsolutePath(currentSelectedItem));
|
||||
}
|
||||
}
|
||||
|
||||
await Tab.SelectNextItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using FileTime.App.Core.Clipboard;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.StateManagement;
|
||||
using FileTime.Core.Timeline;
|
||||
using FileTime.Providers.Local;
|
||||
using FileTime.Providers.Smb;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -21,7 +22,8 @@ namespace FileTime.App.Core
|
||||
.AddSingleton<IContentProvider, LocalContentProvider>(sp => sp.GetService<LocalContentProvider>() ?? throw new Exception($"No {nameof(LocalContentProvider)} instance found"))
|
||||
.AddSingleton<IContentProvider, SmbContentProvider>()
|
||||
.AddSingleton<ElementCreationStates>()
|
||||
.AddSingleton<CommandExecutor>();
|
||||
.AddSingleton<CommandExecutor>()
|
||||
.AddSingleton<TimeRunner>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using FileTime.ConsoleUI.App.UI.Color;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Extensions;
|
||||
using FileTime.Core.Models;
|
||||
@@ -28,8 +27,8 @@ namespace FileTime.ConsoleUI.App
|
||||
private async Task GoUp() => await _selectedTab!.GoUp();
|
||||
private async Task Open() => await _selectedTab!.Open();
|
||||
|
||||
private async Task MoveCursorUpPage() => await _selectedTab!.SelectPreviousItem(_renderers[_selectedTab].PageSize);
|
||||
private async Task MoveCursorDownPage() => await _selectedTab!.SelectNextItem(_renderers[_selectedTab].PageSize);
|
||||
private async Task MoveCursorUpPage() => await _selectedTab!.SelectPreviousItem(UI.Render.PageSize);
|
||||
private async Task MoveCursorDownPage() => await _selectedTab!.SelectNextItem(UI.Render.PageSize);
|
||||
private async Task MoveCursorToTop() => await _selectedTab!.SelectFirstItem();
|
||||
private async Task MoveCursorToBottom() => await _selectedTab!.SelectLastItem();
|
||||
|
||||
@@ -38,22 +37,7 @@ namespace FileTime.ConsoleUI.App
|
||||
const string hiddenFilterName = "filter_showhiddenelements";
|
||||
|
||||
var currentLocation = await _selectedTab!.GetCurrentLocation();
|
||||
|
||||
/*IContainer containerToOpen = currentLocation;
|
||||
|
||||
if (currentLocation is VirtualContainer oldVirtualContainer)
|
||||
{
|
||||
containerToOpen = oldVirtualContainer.HasWithName(hiddenFilterName)
|
||||
? oldVirtualContainer.ExceptWithName(hiddenFilterName)
|
||||
: GenerateHiddenFilterVirtualContainer(currentLocation);
|
||||
}
|
||||
else
|
||||
{
|
||||
containerToOpen = GenerateHiddenFilterVirtualContainer(currentLocation);
|
||||
} */
|
||||
|
||||
var containerToOpen = await currentLocation.ToggleVirtualContainerInChain(hiddenFilterName, GenerateHiddenFilterVirtualContainer);
|
||||
|
||||
await _selectedTab.OpenContainer(containerToOpen);
|
||||
|
||||
static async Task<VirtualContainer> GenerateHiddenFilterVirtualContainer(IContainer container)
|
||||
@@ -81,20 +65,9 @@ namespace FileTime.ConsoleUI.App
|
||||
|
||||
public async Task Select()
|
||||
{
|
||||
var currentLocation = await _selectedTab!.GetCurrentLocation();
|
||||
if (currentLocation != null)
|
||||
if (_selectedTab != null)
|
||||
{
|
||||
var currentSelectedItem = await _selectedTab.GetCurrentSelectedItem()!;
|
||||
if (_paneStates[_selectedTab].ContainsSelectedItem(currentSelectedItem.Provider, currentLocation, currentSelectedItem.FullName!))
|
||||
{
|
||||
_paneStates[_selectedTab].RemoveSelectedItem(currentSelectedItem.Provider, currentLocation, currentSelectedItem.FullName!);
|
||||
}
|
||||
else
|
||||
{
|
||||
_paneStates[_selectedTab].AddSelectedItem(currentSelectedItem.Provider, currentLocation, currentSelectedItem.FullName!);
|
||||
}
|
||||
|
||||
await _selectedTab.SelectNextItem();
|
||||
await _tabStates[_selectedTab].MakrCurrentItem();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,18 +76,18 @@ namespace FileTime.ConsoleUI.App
|
||||
_clipboard.Clear();
|
||||
_clipboard.SetCommand<CopyCommand>();
|
||||
|
||||
var currentSelectedItems = await _paneStates[_selectedTab!].GetCurrentSelectedItems();
|
||||
var currentSelectedItems = await _tabStates[_selectedTab!].GetCurrentMarkedItems();
|
||||
if (currentSelectedItems.Count > 0)
|
||||
{
|
||||
foreach (var selectedItem in currentSelectedItems)
|
||||
{
|
||||
_clipboard.AddContent(selectedItem.ContentProvider, selectedItem.Path);
|
||||
_clipboard.AddContent(new AbsolutePath(selectedItem));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentSelectedItem = (await _selectedTab!.GetCurrentSelectedItem())!;
|
||||
_clipboard.AddContent(currentSelectedItem.Provider, currentSelectedItem.FullName!);
|
||||
_clipboard.AddContent(new AbsolutePath(currentSelectedItem));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +130,7 @@ namespace FileTime.ConsoleUI.App
|
||||
? virtualContainer.BaseContainer
|
||||
: currentLocation;
|
||||
|
||||
_commandExecutor.ExecuteCommand(command);
|
||||
await _timeRunner.AddCommand(command);
|
||||
|
||||
_clipboard.Clear();
|
||||
}
|
||||
@@ -194,9 +167,9 @@ namespace FileTime.ConsoleUI.App
|
||||
|
||||
private async Task HardDelete()
|
||||
{
|
||||
IList<IAbsolutePath>? itemsToDelete = null;
|
||||
IList<AbsolutePath>? itemsToDelete = null;
|
||||
|
||||
var currentSelectedItems = await _paneStates[_selectedTab!].GetCurrentSelectedItems();
|
||||
var currentSelectedItems = (await _tabStates[_selectedTab!].GetCurrentMarkedItems()).Select(p => p.Resolve()).ToList();
|
||||
var currentSelectedItem = await _selectedTab?.GetCurrentSelectedItem();
|
||||
if (currentSelectedItems.Count > 0)
|
||||
{
|
||||
@@ -212,7 +185,7 @@ namespace FileTime.ConsoleUI.App
|
||||
|
||||
if (delete)
|
||||
{
|
||||
itemsToDelete = currentSelectedItems.Cast<IAbsolutePath>().ToList();
|
||||
itemsToDelete = currentSelectedItems.Cast<AbsolutePath>().ToList();
|
||||
}
|
||||
}
|
||||
else if (currentSelectedItem != null)
|
||||
@@ -225,9 +198,9 @@ namespace FileTime.ConsoleUI.App
|
||||
|
||||
if (delete)
|
||||
{
|
||||
itemsToDelete = new List<IAbsolutePath>()
|
||||
itemsToDelete = new List<AbsolutePath>()
|
||||
{
|
||||
new AbsolutePath(currentSelectedItem.Provider, currentSelectedItem.FullName!)
|
||||
new AbsolutePath(currentSelectedItem)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -241,7 +214,7 @@ namespace FileTime.ConsoleUI.App
|
||||
deleteCommand.ItemsToDelete.Add(itemToDelete);
|
||||
}
|
||||
|
||||
_commandExecutor.ExecuteCommand(deleteCommand);
|
||||
await _timeRunner.AddCommand(deleteCommand);
|
||||
_clipboard.Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using FileTime.App.Core.Tab;
|
||||
using FileTime.ConsoleUI.App.UI.Color;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.App.Core.Command;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.ConsoleUI.App
|
||||
{
|
||||
@@ -17,14 +18,14 @@ namespace FileTime.ConsoleUI.App
|
||||
{
|
||||
private readonly List<Tab> _tabs = new();
|
||||
private readonly Dictionary<Tab, Render> _renderers = new();
|
||||
private readonly Dictionary<Tab, TabState> _paneStates = new();
|
||||
private readonly Dictionary<Tab, TabState> _tabStates = new();
|
||||
private Tab? _selectedTab;
|
||||
|
||||
private readonly List<CommandBinding> _commandBindings = new();
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IClipboard _clipboard;
|
||||
private readonly IColoredConsoleRenderer _coloredConsoleRenderer;
|
||||
private readonly CommandExecutor _commandExecutor;
|
||||
private readonly TimeRunner _timeRunner;
|
||||
private readonly ConsoleReader _consoleReader;
|
||||
private readonly IStyles _styles;
|
||||
private readonly List<ConsoleKeyInfo> _previousKeys = new();
|
||||
@@ -35,14 +36,14 @@ namespace FileTime.ConsoleUI.App
|
||||
IServiceProvider serviceProvider,
|
||||
IClipboard clipboard,
|
||||
IColoredConsoleRenderer coloredConsoleRenderer,
|
||||
CommandExecutor commandExecutor,
|
||||
TimeRunner timeRunner,
|
||||
ConsoleReader consoleReader,
|
||||
IStyles styles)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
_clipboard = clipboard;
|
||||
_coloredConsoleRenderer = coloredConsoleRenderer;
|
||||
_commandExecutor = commandExecutor;
|
||||
_timeRunner = timeRunner;
|
||||
_consoleReader = consoleReader;
|
||||
_styles = styles;
|
||||
InitCommandBindings();
|
||||
@@ -60,7 +61,7 @@ namespace FileTime.ConsoleUI.App
|
||||
_tabs.Add(tab);
|
||||
|
||||
var paneState = new TabState(tab);
|
||||
_paneStates.Add(tab, paneState);
|
||||
_tabStates.Add(tab, paneState);
|
||||
|
||||
var renderer = _serviceProvider.GetService<Render>()!;
|
||||
renderer.Init(tab, paneState);
|
||||
@@ -73,7 +74,7 @@ namespace FileTime.ConsoleUI.App
|
||||
{
|
||||
_tabs.Remove(pane);
|
||||
_renderers.Remove(pane);
|
||||
_paneStates.Remove(pane);
|
||||
_tabStates.Remove(pane);
|
||||
}
|
||||
|
||||
private void InitCommandBindings()
|
||||
@@ -134,7 +135,7 @@ namespace FileTime.ConsoleUI.App
|
||||
Cut),
|
||||
new CommandBinding(
|
||||
"paste (merge)",
|
||||
Commands.Paste,
|
||||
Commands.PasteMerge,
|
||||
new[]
|
||||
{
|
||||
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||
@@ -143,7 +144,7 @@ namespace FileTime.ConsoleUI.App
|
||||
PasteMerge),
|
||||
new CommandBinding(
|
||||
"paste (overwrite)",
|
||||
Commands.Paste,
|
||||
Commands.PasteOverwrite,
|
||||
new[]
|
||||
{
|
||||
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||
@@ -152,7 +153,7 @@ namespace FileTime.ConsoleUI.App
|
||||
PasteOverwrite),
|
||||
new CommandBinding(
|
||||
"paste (skip)",
|
||||
Commands.Paste,
|
||||
Commands.PasteSkip,
|
||||
new[]
|
||||
{
|
||||
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace FileTime.ConsoleUI.App.UI
|
||||
public Tab Tab { get; private set; }
|
||||
public TabState TabState { get; private set; }
|
||||
|
||||
public int PageSize => Console.WindowHeight - _contentPaddingTop - _contentPaddingBottom;
|
||||
public static int PageSize => Console.WindowHeight - _contentPaddingTop - _contentPaddingBottom;
|
||||
public Render(IColoredConsoleRenderer coloredRenderer, IStyles appStyle)
|
||||
{
|
||||
_coloredRenderer = coloredRenderer;
|
||||
@@ -219,7 +219,7 @@ namespace FileTime.ConsoleUI.App.UI
|
||||
}
|
||||
}
|
||||
|
||||
var isSelected = TabState.ContainsSelectedItem(item.Provider, currentContainer, item.FullName!);
|
||||
var isSelected = TabState.ContainsMarkedItem(currentContainer, new AbsolutePath(item));
|
||||
if (isSelected)
|
||||
{
|
||||
backgroundColor = _appStyle.SelectedItemBackground;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace AsyncEvent
|
||||
{
|
||||
public class AsyncEventHandler<TSender, TArg> where TArg : AsyncEventArgs
|
||||
public class AsyncEventHandler<TSender, TArg>
|
||||
{
|
||||
private readonly List<Func<TSender, TArg, Task>> _handlers;
|
||||
private readonly Action<Func<TSender, TArg, Task>> _add;
|
||||
@@ -56,7 +56,7 @@
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
public class AsyncEventHandler<TArg> : AsyncEventHandler<object?, AsyncEventArgs> where TArg : AsyncEventArgs
|
||||
public class AsyncEventHandler<TArg> : AsyncEventHandler<object?, TArg>
|
||||
{
|
||||
public AsyncEventHandler(Action<Func<object?, TArg, Task>>? add = null, Action<Func<object?, TArg, Task>>? remove = null) : base(add, remove) { }
|
||||
}
|
||||
|
||||
9
src/Core/FileTime.Core/Command/CanCommandRun.cs
Normal file
9
src/Core/FileTime.Core/Command/CanCommandRun.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public enum CanCommandRun
|
||||
{
|
||||
True,
|
||||
False,
|
||||
Forceable
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CommandExecutor
|
||||
@@ -9,15 +11,15 @@ namespace FileTime.Core.Command
|
||||
_commandHandlers = commandHandlers.ToList();
|
||||
}
|
||||
|
||||
public void ExecuteCommand(ICommand command)
|
||||
public async Task ExecuteCommandAsync(ICommand command, TimeRunner timeRunner)
|
||||
{
|
||||
if (command is IExecutableCommand executableCommand)
|
||||
{
|
||||
executableCommand.Execute();
|
||||
await executableCommand.Execute(timeRunner);
|
||||
}
|
||||
else
|
||||
{
|
||||
_commandHandlers.Find(c => c.CanHandle(command))?.Execute(command);
|
||||
await _commandHandlers.Find(c => c.CanHandle(command))?.ExecuteAsync(command, timeRunner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,59 +5,115 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public class CopyCommand : ITransportationCommand
|
||||
{
|
||||
public IList<IAbsolutePath> Sources { get; } = new List<IAbsolutePath>();
|
||||
private Action<AbsolutePath, AbsolutePath>? _copyOperation;
|
||||
private Func<IContainer, string, Task<IContainer>>? _createContainer;
|
||||
private TimeRunner? _timeRunner;
|
||||
|
||||
public IList<AbsolutePath>? Sources { get; } = new List<AbsolutePath>();
|
||||
|
||||
public IContainer? Target { get; set; }
|
||||
|
||||
public TransportMode TransportMode { get; set; } = TransportMode.Merge;
|
||||
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
|
||||
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public async Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
var newDiffs = new List<Difference>();
|
||||
|
||||
_copyOperation = (_, to) =>
|
||||
{
|
||||
var target = to.GetParentAsAbsolutePath().Resolve();
|
||||
newDiffs.Add(new Difference(
|
||||
target is IElement
|
||||
? DifferenceItemType.Element
|
||||
: DifferenceItemType.Container,
|
||||
DifferenceActionType.Create,
|
||||
to
|
||||
));
|
||||
};
|
||||
|
||||
_createContainer = async (IContainer target, string name) =>
|
||||
{
|
||||
var newContainerDiff = new Difference(
|
||||
DifferenceItemType.Container,
|
||||
DifferenceActionType.Create,
|
||||
AbsolutePath.FromParentAndChildName(target, name)
|
||||
);
|
||||
|
||||
newDiffs.Add(newContainerDiff);
|
||||
|
||||
return (IContainer)(await newContainerDiff.AbsolutePath.Resolve())!;
|
||||
};
|
||||
|
||||
await DoCopy(Sources, Target, TransportMode.Value);
|
||||
|
||||
return startPoint.WithDifferences(newDiffs);
|
||||
}
|
||||
|
||||
public async Task Execute(Action<IAbsolutePath, IAbsolutePath> copy)
|
||||
public async Task Execute(Action<AbsolutePath, AbsolutePath> copy, TimeRunner timeRunner)
|
||||
{
|
||||
await DoCopy(Sources, Target, TransportMode, copy);
|
||||
if (Sources == null) throw new ArgumentException(nameof(Sources) + " can not be null");
|
||||
if (Target == null) throw new ArgumentException(nameof(Target) + " can not be null");
|
||||
if (TransportMode == null) throw new ArgumentException(nameof(TransportMode) + " can not be null");
|
||||
|
||||
_copyOperation = copy;
|
||||
_createContainer = async (IContainer target, string name) => await target.CreateContainer(name);
|
||||
_timeRunner = timeRunner;
|
||||
|
||||
await DoCopy(Sources, Target, TransportMode.Value);
|
||||
}
|
||||
|
||||
private async Task DoCopy(IEnumerable<IAbsolutePath> sources, IContainer target, TransportMode transportMode, Action<IAbsolutePath, IAbsolutePath> copy)
|
||||
private async Task DoCopy(
|
||||
IEnumerable<AbsolutePath> sources,
|
||||
IContainer target,
|
||||
TransportMode transportMode)
|
||||
{
|
||||
if (_copyOperation == null) throw new ArgumentException("No copy operation were given.");
|
||||
if (_createContainer == null) throw new ArgumentException("No container creation function were given.");
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
var item = await source.ContentProvider.GetByPath(source.Path);
|
||||
var item = await source.Resolve();
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
var targetContainer = (await target.GetContainers())?.FirstOrDefault(d => d.Name == container.Name) ?? (await target.CreateContainer(container.Name)!);
|
||||
var targetContainer = (await target.GetContainers())?.FirstOrDefault(d => d.Name == container.Name) ?? (await _createContainer?.Invoke(target, container.Name)!);
|
||||
|
||||
var childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(item.Provider, d.FullName!));
|
||||
var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(item.Provider, f.FullName!));
|
||||
var childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(d));
|
||||
var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(f));
|
||||
|
||||
await DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode, copy);
|
||||
await DoCopy(childDirectories.Concat(childFiles), targetContainer, transportMode);
|
||||
_timeRunner?.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
var targetName = element.Name;
|
||||
|
||||
var targetNameExists = await target.IsExists(targetName);
|
||||
if (transportMode == TransportMode.Merge)
|
||||
if (transportMode == Command.TransportMode.Merge)
|
||||
{
|
||||
for (var i = 0; targetNameExists; i++)
|
||||
{
|
||||
targetName = element.Name + (i == 0 ? "_" : $"_{i}");
|
||||
}
|
||||
}
|
||||
else if (transportMode == TransportMode.Skip && targetNameExists)
|
||||
else if (transportMode == Command.TransportMode.Skip && targetNameExists)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var targetPath = target.FullName + Constants.SeparatorChar + targetName;
|
||||
|
||||
copy(new AbsolutePath(source.ContentProvider, element.FullName!), new AbsolutePath(target.Provider, targetPath));
|
||||
_copyOperation?.Invoke(new AbsolutePath(element), AbsolutePath.FromParentAndChildName(target, targetName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
//TODO: implement
|
||||
return Task.FromResult(CanCommandRun.True);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,51 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CreateContainerCommand : ICommand
|
||||
public class CreateContainerCommand : IExecutableCommand
|
||||
{
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public AbsolutePath Container { get; }
|
||||
public string NewContainerName { get; }
|
||||
|
||||
public CreateContainerCommand(AbsolutePath container, string newContainerName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Container = container;
|
||||
NewContainerName = newContainerName;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var possibleContainer = await Container.Resolve();
|
||||
if (possibleContainer is IContainer container)
|
||||
{
|
||||
await container.CreateContainer(NewContainerName);
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
//TODO: else
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
var newDifferences = new List<Difference>()
|
||||
{
|
||||
new Difference(DifferenceItemType.Container, DifferenceActionType.Create, new AbsolutePath(Container.ContentProvider, Container.Path + Constants.SeparatorChar + NewContainerName, Container.VirtualContentProvider))
|
||||
};
|
||||
return Task.FromResult(startPoint.WithDifferences(newDifferences));
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var resolvedContainer = await Container.Resolve();
|
||||
if (resolvedContainer == null) return CanCommandRun.Forceable;
|
||||
|
||||
if (resolvedContainer is not IContainer container
|
||||
|| await container.IsExists(NewContainerName))
|
||||
{
|
||||
return CanCommandRun.False;
|
||||
}
|
||||
|
||||
return CanCommandRun.True;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,50 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class CreateElementCommand : ICommand
|
||||
public class CreateElementCommand : IExecutableCommand
|
||||
{
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public AbsolutePath Container { get; }
|
||||
public string NewElementName { get; }
|
||||
|
||||
public CreateElementCommand(AbsolutePath container, string newElementName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
Container = container;
|
||||
NewElementName = newElementName;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var possibleContainer = await Container.Resolve();
|
||||
if (possibleContainer is IContainer container)
|
||||
{
|
||||
await container.CreateElement(NewElementName);
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
var newDifferences = new List<Difference>()
|
||||
{
|
||||
new Difference(DifferenceItemType.Element, DifferenceActionType.Create, new AbsolutePath(Container.ContentProvider, Container.Path + Constants.SeparatorChar + NewElementName, Container.VirtualContentProvider))
|
||||
};
|
||||
return Task.FromResult(startPoint.WithDifferences(newDifferences));
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var resolvedContainer = Container.Resolve();
|
||||
if (resolvedContainer == null) return CanCommandRun.Forceable;
|
||||
|
||||
if (resolvedContainer is not IContainer container
|
||||
|| await container.IsExists(NewElementName))
|
||||
{
|
||||
return CanCommandRun.False;
|
||||
}
|
||||
|
||||
return CanCommandRun.True;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using FileTime.Core.Extensions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
@@ -5,37 +6,64 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public class DeleteCommand : IExecutableCommand
|
||||
{
|
||||
public IList<IAbsolutePath> ItemsToDelete { get; } = new List<IAbsolutePath>();
|
||||
public IList<AbsolutePath> ItemsToDelete { get; } = new List<AbsolutePath>();
|
||||
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public async Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
var newDifferences = new List<Difference>();
|
||||
|
||||
foreach (var itemToDelete in ItemsToDelete)
|
||||
{
|
||||
var item = await itemToDelete.Resolve();
|
||||
newDifferences.Add(new Difference(
|
||||
item.ToDifferenceItemType(),
|
||||
DifferenceActionType.Delete,
|
||||
itemToDelete
|
||||
));
|
||||
}
|
||||
return startPoint.WithDifferences(newDifferences);
|
||||
}
|
||||
|
||||
public async Task Execute()
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
foreach (var item in ItemsToDelete)
|
||||
{
|
||||
await DoDelete(await item.ContentProvider.GetByPath(item.Path)!);
|
||||
await DoDelete((await item.Resolve())!, timeRunner);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DoDelete(IItem item)
|
||||
private async Task DoDelete(IItem item, TimeRunner timeRunner)
|
||||
{
|
||||
if (item is IContainer container)
|
||||
{
|
||||
foreach (var child in await container.GetItems())
|
||||
foreach (var child in (await container.GetItems())!)
|
||||
{
|
||||
await DoDelete(child);
|
||||
await DoDelete(child, timeRunner);
|
||||
await child.Delete();
|
||||
}
|
||||
|
||||
await item.Delete();
|
||||
await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(container));
|
||||
}
|
||||
else if(item is IElement element)
|
||||
else if (item is IElement element)
|
||||
{
|
||||
await element.Delete();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
var result = CanCommandRun.True;
|
||||
foreach (var itemPath in ItemsToDelete)
|
||||
{
|
||||
var resolvedItem = await itemPath.Resolve();
|
||||
if (!(resolvedItem?.CanDelete ?? true))
|
||||
{
|
||||
result = CanCommandRun.Forceable;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public interface ICommand
|
||||
{
|
||||
PointInTime SimulateCommand(PointInTime moment);
|
||||
Task<CanCommandRun> CanRun(PointInTime startPoint);
|
||||
Task<PointInTime> SimulateCommand(PointInTime startPoint);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public interface ICommandHandler
|
||||
{
|
||||
bool CanHandle(object command);
|
||||
void Execute(object command);
|
||||
Task ExecuteAsync(object command, TimeRunner timeRunner);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public interface IExecutableCommand : ICommand
|
||||
{
|
||||
Task Execute();
|
||||
Task Execute(TimeRunner timeRunner);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public interface ITransportationCommand : ICommand
|
||||
{
|
||||
IList<IAbsolutePath> Sources { get; }
|
||||
IContainer Target { get; set;}
|
||||
TransportMode TransportMode { get; set; }
|
||||
IList<AbsolutePath>? Sources { get; }
|
||||
IContainer? Target { get; set;}
|
||||
TransportMode? TransportMode { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,17 @@ namespace FileTime.Core.Command
|
||||
{
|
||||
public class MoveCommand : ITransportationCommand
|
||||
{
|
||||
public IList<IAbsolutePath> Sources { get; } = new List<IAbsolutePath>();
|
||||
public IList<AbsolutePath>? Sources { get; } = new List<AbsolutePath>();
|
||||
|
||||
public IContainer? Target { get; set; }
|
||||
public TransportMode TransportMode { get; set; } = TransportMode.Merge;
|
||||
public TransportMode? TransportMode { get; set; } = Command.TransportMode.Merge;
|
||||
|
||||
public PointInTime SimulateCommand(PointInTime delta)
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
37
src/Core/FileTime.Core/Command/RenameCommand.cs
Normal file
37
src/Core/FileTime.Core/Command/RenameCommand.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Command
|
||||
{
|
||||
public class RenameCommand : IExecutableCommand
|
||||
{
|
||||
public AbsolutePath Source { get; }
|
||||
public string Target { get; }
|
||||
|
||||
public RenameCommand(AbsolutePath source, string target)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public async Task Execute(TimeRunner timeRunner)
|
||||
{
|
||||
var itemToRename = await Source.Resolve();
|
||||
if (itemToRename != null)
|
||||
{
|
||||
await itemToRename.Rename(Target);
|
||||
timeRunner.RefreshContainer?.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<PointInTime> SimulateCommand(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<CanCommandRun> CanRun(PointInTime startPoint)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,8 @@ namespace FileTime.Core.Components
|
||||
{
|
||||
private IItem? _currentSelectedItem;
|
||||
private IContainer _currentLocation;
|
||||
|
||||
private string? _lastPath;
|
||||
|
||||
public int CurrentSelectedIndex { get; private set; }
|
||||
|
||||
public AsyncEventHandler CurrentLocationChanged = new();
|
||||
@@ -33,10 +34,10 @@ namespace FileTime.Core.Components
|
||||
}
|
||||
|
||||
_currentLocation = value;
|
||||
await CurrentLocationChanged?.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
await CurrentLocationChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
await SetCurrentSelectedItem(currentLocationItems.Count > 0 ? currentLocationItems[0] : null);
|
||||
await SetCurrentSelectedItem(await GetItemByLastPath() ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null));
|
||||
_currentLocation.Refreshed.Add(HandleCurrentLocationRefresh);
|
||||
}
|
||||
}
|
||||
@@ -58,14 +59,71 @@ namespace FileTime.Core.Components
|
||||
}
|
||||
|
||||
_currentSelectedItem = itemToSelect;
|
||||
_lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName);
|
||||
CurrentSelectedIndex = await GetItemIndex(itemToSelect);
|
||||
await CurrentSelectedItemChanged?.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
await CurrentSelectedItemChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
}
|
||||
}
|
||||
public async Task<IItem?> GetItemByLastPath(IContainer? container = null)
|
||||
{
|
||||
container ??= _currentLocation;
|
||||
var containerFullName = container.FullName;
|
||||
|
||||
if (_lastPath == null
|
||||
|| !container.IsLoaded
|
||||
|| (containerFullName != null && !_lastPath.StartsWith(containerFullName))
|
||||
)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var itemNameToSelect = _lastPath
|
||||
.Split(Constants.SeparatorChar)
|
||||
.Skip(
|
||||
containerFullName == null
|
||||
? 0
|
||||
: containerFullName
|
||||
.Split(Constants.SeparatorChar)
|
||||
.Count())
|
||||
.FirstOrDefault();
|
||||
|
||||
return (await container.GetItems())?.FirstOrDefault(i => i.Name == itemNameToSelect);
|
||||
}
|
||||
|
||||
private string GetCommonPath(string? oldPath, string? newPath)
|
||||
{
|
||||
var oldPathParts = oldPath?.Split(Constants.SeparatorChar) ?? new string[0];
|
||||
var newPathParts = newPath?.Split(Constants.SeparatorChar) ?? new string[0];
|
||||
|
||||
var commonPathParts = new List<string>();
|
||||
|
||||
var max = oldPathParts.Length > newPathParts.Length ? oldPathParts.Length : newPathParts.Length;
|
||||
|
||||
for (var i = 0; i < max; i++)
|
||||
{
|
||||
if (newPathParts.Length <= i)
|
||||
{
|
||||
commonPathParts.AddRange(oldPathParts.Skip(i));
|
||||
break;
|
||||
}
|
||||
else if (oldPathParts.Length <= i || oldPathParts[i] != newPathParts[i])
|
||||
{
|
||||
commonPathParts.AddRange(newPathParts.Skip(i));
|
||||
break;
|
||||
}
|
||||
else if (oldPathParts[i] == newPathParts[i])
|
||||
{
|
||||
commonPathParts.Add(oldPathParts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return string.Join(Constants.SeparatorChar, commonPathParts);
|
||||
}
|
||||
|
||||
private async Task HandleCurrentLocationRefresh(object? sender, AsyncEventArgs e)
|
||||
{
|
||||
var currentSelectedName = (await GetCurrentSelectedItem())?.FullName;
|
||||
var currentSelectedName = (await GetCurrentSelectedItem())?.FullName ?? (await GetItemByLastPath()).FullName;
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
if (currentSelectedName != null)
|
||||
{
|
||||
|
||||
15
src/Core/FileTime.Core/Extensions/TimelineExtensions.cs
Normal file
15
src/Core/FileTime.Core/Extensions/TimelineExtensions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Extensions
|
||||
{
|
||||
public static class TimelineExtensions
|
||||
{
|
||||
public static DifferenceItemType ToDifferenceItemType(this IItem? item)
|
||||
{
|
||||
if (item is IContainer) return DifferenceItemType.Container;
|
||||
else if (item is IElement) return DifferenceItemType.Element;
|
||||
else return DifferenceItemType.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,13 @@ namespace FileTime.Core.Interactions
|
||||
{
|
||||
public string Text { get; }
|
||||
public InputType InputType { get; }
|
||||
public string? DefaultValue { get; }
|
||||
|
||||
public InputElement(string text, InputType inputType)
|
||||
public InputElement(string text, InputType inputType, string? defaultValue = null)
|
||||
{
|
||||
Text = text;
|
||||
InputType = inputType;
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ namespace FileTime.Core.Interactions
|
||||
public enum InputType
|
||||
{
|
||||
Text,
|
||||
Password
|
||||
Password,
|
||||
Bool
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,97 @@
|
||||
using FileTime.Core.Providers;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public class AbsolutePath : IAbsolutePath
|
||||
public sealed class AbsolutePath
|
||||
{
|
||||
public IContentProvider ContentProvider { get; }
|
||||
public IContentProvider? VirtualContentProvider { get; }
|
||||
|
||||
public string Path { get; }
|
||||
|
||||
public AbsolutePath(IContentProvider contentProvider, string path)
|
||||
public AbsolutePath(AbsolutePath from)
|
||||
{
|
||||
ContentProvider = from.ContentProvider;
|
||||
Path = from.Path;
|
||||
VirtualContentProvider = from.VirtualContentProvider;
|
||||
}
|
||||
|
||||
public AbsolutePath(IContentProvider contentProvider, string path, IContentProvider? virtualContentProvider)
|
||||
{
|
||||
ContentProvider = contentProvider;
|
||||
Path = path;
|
||||
VirtualContentProvider = virtualContentProvider;
|
||||
}
|
||||
|
||||
public AbsolutePath(IItem item)
|
||||
{
|
||||
if (item is TimeContainer timeContainer)
|
||||
{
|
||||
ContentProvider = timeContainer.Provider;
|
||||
VirtualContentProvider = timeContainer.VirtualProvider;
|
||||
Path = timeContainer.FullName!;
|
||||
}
|
||||
else if (item is TimeElement timeElement)
|
||||
{
|
||||
ContentProvider = timeElement.Provider;
|
||||
VirtualContentProvider = timeElement.VirtualProvider;
|
||||
Path = timeElement.FullName!;
|
||||
}
|
||||
else
|
||||
{
|
||||
ContentProvider = item.Provider;
|
||||
Path = item.FullName!;
|
||||
}
|
||||
}
|
||||
|
||||
public static AbsolutePath FromParentAndChildName(IContainer parent, string childName)
|
||||
{
|
||||
IContentProvider? contentProvider;
|
||||
IContentProvider? virtualContentProvider;
|
||||
string? path;
|
||||
|
||||
if (parent is TimeContainer timeContainer)
|
||||
{
|
||||
contentProvider = timeContainer.Provider;
|
||||
virtualContentProvider = timeContainer.VirtualProvider;
|
||||
path = timeContainer.FullName! + Constants.SeparatorChar + childName;
|
||||
}
|
||||
else
|
||||
{
|
||||
contentProvider = parent.Provider;
|
||||
path = parent.FullName! + Constants.SeparatorChar + childName;
|
||||
virtualContentProvider = null;
|
||||
}
|
||||
|
||||
return new AbsolutePath(contentProvider, path, virtualContentProvider);
|
||||
}
|
||||
|
||||
public bool IsEqual(AbsolutePath path)
|
||||
{
|
||||
//TODO: sure??
|
||||
return path.ContentProvider == ContentProvider && path.Path == Path;
|
||||
}
|
||||
|
||||
public async Task<IItem?> Resolve()
|
||||
{
|
||||
var result = VirtualContentProvider != null && (await VirtualContentProvider.IsExists(Path))
|
||||
? await VirtualContentProvider.GetByPath(Path)
|
||||
: null;
|
||||
|
||||
result ??= await ContentProvider.GetByPath(Path);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public string GetParent()
|
||||
{
|
||||
var pathParts = Path.Split(Constants.SeparatorChar);
|
||||
return string.Join(Constants.SeparatorChar, pathParts);
|
||||
}
|
||||
|
||||
public AbsolutePath GetParentAsAbsolutePath() => new(ContentProvider, GetParent(), VirtualContentProvider);
|
||||
|
||||
public string GetName() => Path.Split(Constants.SeparatorChar).Last();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public interface IAbsolutePath
|
||||
{
|
||||
IContentProvider ContentProvider { get; }
|
||||
string Path { get; }
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,6 @@ namespace FileTime.Core.Models
|
||||
Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default);
|
||||
|
||||
Task Refresh();
|
||||
IContainer? GetParent();
|
||||
Task<IItem?> GetByPath(string path);
|
||||
Task<IContainer> CreateContainer(string name);
|
||||
Task<IElement> CreateElement(string name);
|
||||
@@ -18,6 +17,8 @@ namespace FileTime.Core.Models
|
||||
|
||||
Task<IContainer> Clone();
|
||||
|
||||
bool IsLoaded { get; }
|
||||
|
||||
AsyncEventHandler Refreshed { get; }
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,11 @@ namespace FileTime.Core.Models
|
||||
string Name { get; }
|
||||
string? FullName { get; }
|
||||
bool IsHidden { get; }
|
||||
bool CanDelete { get; }
|
||||
bool CanRename { get; }
|
||||
IContentProvider Provider { get; }
|
||||
Task Delete();
|
||||
Task Rename(string newName);
|
||||
IContainer? GetParent();
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ namespace FileTime.Core.Models
|
||||
public string? FullName => BaseContainer.FullName;
|
||||
|
||||
public bool IsHidden => BaseContainer.IsHidden;
|
||||
public bool IsLoaded => BaseContainer.IsLoaded;
|
||||
public bool CanDelete => BaseContainer.CanDelete;
|
||||
public bool CanRename => BaseContainer.CanRename;
|
||||
|
||||
public IContentProvider Provider => BaseContainer.Provider;
|
||||
|
||||
@@ -159,5 +162,7 @@ namespace FileTime.Core.Models
|
||||
VirtualContainerName
|
||||
);
|
||||
}
|
||||
|
||||
public async Task Rename(string newName) => await BaseContainer.Rename(newName);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
using System;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
@@ -16,8 +17,11 @@ namespace FileTime.Core.Providers
|
||||
public string? FullName => null;
|
||||
|
||||
public bool IsHidden => false;
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public IContentProvider Provider => null;
|
||||
public bool CanDelete => false;
|
||||
public bool CanRename => false;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
@@ -52,5 +56,7 @@ namespace FileTime.Core.Providers
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
|
||||
|
||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
27
src/Core/FileTime.Core/Timeline/CommandTimeState.cs
Normal file
27
src/Core/FileTime.Core/Timeline/CommandTimeState.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using FileTime.Core.Command;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class CommandTimeState
|
||||
{
|
||||
public ICommand Command { get; }
|
||||
public CanCommandRun CanRun { get; private set; } = CanCommandRun.False;
|
||||
public bool ForceRun { get; set; }
|
||||
public TimeProvider? TimeProvider { get; private set; }
|
||||
|
||||
public CommandTimeState(ICommand command, PointInTime? startTime)
|
||||
{
|
||||
Command = command;
|
||||
UpdateState(startTime).Wait();
|
||||
}
|
||||
|
||||
public async Task UpdateState(PointInTime? startPoint)
|
||||
{
|
||||
CanRun = startPoint == null ? CanCommandRun.False : await Command.CanRun(startPoint);
|
||||
if (startPoint != null)
|
||||
{
|
||||
TimeProvider = startPoint.Provider as TimeProvider;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ContainerSnapshot
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
31
src/Core/FileTime.Core/Timeline/Difference.cs
Normal file
31
src/Core/FileTime.Core/Timeline/Difference.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class Difference
|
||||
{
|
||||
public DifferenceItemType Type { get; }
|
||||
public string Name { get; }
|
||||
public AbsolutePath AbsolutePath { get; }
|
||||
public DifferenceActionType Action { get; }
|
||||
|
||||
public Difference(DifferenceItemType type, DifferenceActionType action, AbsolutePath absolutePath)
|
||||
{
|
||||
Type = type;
|
||||
AbsolutePath = absolutePath;
|
||||
Action = action;
|
||||
|
||||
Name = absolutePath.GetName();
|
||||
}
|
||||
|
||||
public Difference WithVirtualContentProvider(IContentProvider? virtualContentProvider)
|
||||
{
|
||||
return new Difference(
|
||||
Type,
|
||||
Action,
|
||||
new AbsolutePath(AbsolutePath.ContentProvider, AbsolutePath.Path, virtualContentProvider)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/Core/FileTime.Core/Timeline/DifferenceActionType.cs
Normal file
8
src/Core/FileTime.Core/Timeline/DifferenceActionType.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public enum DifferenceActionType
|
||||
{
|
||||
Create,
|
||||
Delete
|
||||
}
|
||||
}
|
||||
9
src/Core/FileTime.Core/Timeline/DifferenceItemType.cs
Normal file
9
src/Core/FileTime.Core/Timeline/DifferenceItemType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public enum DifferenceItemType
|
||||
{
|
||||
Container,
|
||||
Element,
|
||||
Unknown
|
||||
}
|
||||
}
|
||||
89
src/Core/FileTime.Core/Timeline/ParallelCommands.cs
Normal file
89
src/Core/FileTime.Core/Timeline/ParallelCommands.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using FileTime.Core.Command;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ParallelCommands
|
||||
{
|
||||
private static ushort _idCounter;
|
||||
public List<CommandTimeState> _commands;
|
||||
public ushort Id { get; }
|
||||
public IReadOnlyList<CommandTimeState> Commands { get; }
|
||||
public PointInTime? Result { get; private set; }
|
||||
|
||||
public ParallelCommands(PointInTime? result)
|
||||
: this(new List<CommandTimeState>(), result) { }
|
||||
|
||||
private ParallelCommands(List<CommandTimeState> commands, PointInTime? result)
|
||||
{
|
||||
Id = _idCounter++;
|
||||
|
||||
_commands = commands;
|
||||
Commands = _commands.AsReadOnly();
|
||||
|
||||
Result = result;
|
||||
}
|
||||
|
||||
public static async Task<ParallelCommands> Create(PointInTime? startTime, IEnumerable<ICommand> commands)
|
||||
{
|
||||
var commandStates = new List<CommandTimeState>();
|
||||
var currentTime = startTime;
|
||||
foreach (var command in commands)
|
||||
{
|
||||
CommandTimeState commandTimeState = new(command, currentTime);
|
||||
if (currentTime != null)
|
||||
{
|
||||
var canRun = await command.CanRun(currentTime);
|
||||
if (canRun == CanCommandRun.True)
|
||||
{
|
||||
currentTime = await command.SimulateCommand(currentTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
currentTime = null;
|
||||
}
|
||||
}
|
||||
|
||||
commandStates.Add(commandTimeState);
|
||||
}
|
||||
|
||||
return new ParallelCommands(commandStates, currentTime);
|
||||
}
|
||||
|
||||
public async Task AddCommand(ICommand command)
|
||||
{
|
||||
_commands.Add(new CommandTimeState(command, Result));
|
||||
if (Result != null)
|
||||
{
|
||||
Result = await command.SimulateCommand(Result);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<PointInTime?> RefreshResult(PointInTime? startPoint)
|
||||
{
|
||||
var result = startPoint;
|
||||
foreach (var command in _commands)
|
||||
{
|
||||
await command.UpdateState(result);
|
||||
if (result != null)
|
||||
{
|
||||
var canRun = await command.Command.CanRun(result);
|
||||
if (canRun == CanCommandRun.True || (canRun == CanCommandRun.Forceable && command.ForceRun))
|
||||
{
|
||||
result = await command.Command.SimulateCommand(result);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result = result;
|
||||
return Result;
|
||||
}
|
||||
|
||||
public void RemoveAt(int number) => _commands.RemoveAt(number);
|
||||
|
||||
internal void Remove(CommandTimeState command) => _commands.Remove(command);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,40 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class PointInTime
|
||||
public sealed class PointInTime
|
||||
{
|
||||
private readonly Dictionary<IContentProvider, RootSnapshot> snapshots = new();
|
||||
private readonly List<Difference> _differences;
|
||||
|
||||
public IReadOnlyDictionary<IContentProvider, RootSnapshot> Snapshots => new Lazy<IReadOnlyDictionary<IContentProvider, RootSnapshot>>(() => new ReadOnlyDictionary<IContentProvider, RootSnapshot>(snapshots)).Value;
|
||||
public IReadOnlyList<Difference> Differences { get; }
|
||||
|
||||
public IContentProvider? Provider { get; }
|
||||
|
||||
private PointInTime() : this(new List<Difference>(), null) { }
|
||||
|
||||
private PointInTime(IEnumerable<Difference> differences, IContentProvider? provider)
|
||||
{
|
||||
_differences = new List<Difference>(differences);
|
||||
Differences = _differences.AsReadOnly();
|
||||
Provider = provider;
|
||||
}
|
||||
|
||||
private PointInTime(PointInTime previous, IEnumerable<Difference> differences, IContentProvider provider)
|
||||
: this(MergeDifferences(previous.Differences, differences, provider), provider) { }
|
||||
|
||||
public PointInTime WithDifferences(IEnumerable<Difference> differences) => new(this, differences, new TimeProvider(this));
|
||||
|
||||
private static List<Difference> MergeDifferences(IEnumerable<Difference> previouses, IEnumerable<Difference> differences, IContentProvider virtualProvider)
|
||||
{
|
||||
var merged = new List<Difference>();
|
||||
|
||||
merged.AddRange(previouses.Select(p => p.WithVirtualContentProvider(virtualProvider)));
|
||||
merged.AddRange(differences.Select(d => d.WithVirtualContentProvider(virtualProvider)));
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
public static PointInTime CreateEmpty(IContentProvider? parentProvder = null) =>
|
||||
parentProvder == null ? new PointInTime() : new PointInTime(new List<Difference>(), parentProvder);
|
||||
}
|
||||
}
|
||||
18
src/Core/FileTime.Core/Timeline/ReadOnlyCommandTimeState.cs
Normal file
18
src/Core/FileTime.Core/Timeline/ReadOnlyCommandTimeState.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using FileTime.Core.Command;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ReadOnlyCommandTimeState
|
||||
{
|
||||
public CanCommandRun CanRun { get; }
|
||||
public bool ForceRun { get; }
|
||||
public ICommand Command { get; }
|
||||
|
||||
public ReadOnlyCommandTimeState(CommandTimeState commandTimeState)
|
||||
{
|
||||
CanRun = commandTimeState.CanRun;
|
||||
ForceRun = commandTimeState.ForceRun;
|
||||
Command = commandTimeState.Command;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs
Normal file
11
src/Core/FileTime.Core/Timeline/ReadOnlyParallelCommands.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class ReadOnlyParallelCommands
|
||||
{
|
||||
public IReadOnlyList<ReadOnlyCommandTimeState> Commands { get; }
|
||||
public ReadOnlyParallelCommands(ParallelCommands parallelCommands)
|
||||
{
|
||||
Commands = parallelCommands.Commands.Select(c => new ReadOnlyCommandTimeState(c)).ToList().AsReadOnly();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class RootSnapshot
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
121
src/Core/FileTime.Core/Timeline/TimeContainer.cs
Normal file
121
src/Core/FileTime.Core/Timeline/TimeContainer.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeContainer : IContainer
|
||||
{
|
||||
private readonly IContainer? _parent;
|
||||
private readonly PointInTime _pointInTime;
|
||||
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new AsyncEventHandler();
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => true;
|
||||
|
||||
public bool CanRename => true;
|
||||
|
||||
public IContentProvider Provider { get; }
|
||||
public IContentProvider VirtualProvider { get; }
|
||||
|
||||
public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
|
||||
{
|
||||
_parent = parent;
|
||||
_pointInTime = pointInTime;
|
||||
|
||||
Name = name;
|
||||
Provider = contentProvider;
|
||||
VirtualProvider = virtualContentProvider;
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
}
|
||||
|
||||
public async Task<IContainer> Clone() => new TimeContainer(Name, await _parent!.Clone(), Provider, VirtualProvider, _pointInTime);
|
||||
|
||||
public Task<IContainer> CreateContainer(string name) => Task.FromResult((IContainer)new TimeContainer(name, this, Provider, VirtualProvider, _pointInTime));
|
||||
|
||||
public Task<IElement> CreateElement(string name) => Task.FromResult((IElement)new TimeElement(name, this, Provider, VirtualProvider));
|
||||
|
||||
public Task Delete() => Task.CompletedTask;
|
||||
|
||||
public async Task<IItem?> GetByPath(string path)
|
||||
{
|
||||
var paths = path.Split(Constants.SeparatorChar);
|
||||
|
||||
var item = (await GetItems())!.FirstOrDefault(i => i.Name == paths[0]);
|
||||
|
||||
if (paths.Length == 1)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
|
||||
if (item is IContainer container)
|
||||
{
|
||||
return await container.GetByPath(string.Join(Constants.SeparatorChar, paths.Skip(1)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) =>
|
||||
Task.FromResult(
|
||||
(IReadOnlyList<IContainer>?)_pointInTime
|
||||
.Differences
|
||||
.Where(d =>
|
||||
d.Type == DifferenceItemType.Container
|
||||
&& GetParentPath(d.AbsolutePath.Path) == FullName)
|
||||
.Select(MapContainer)
|
||||
.ToList()
|
||||
.AsReadOnly()
|
||||
);
|
||||
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) =>
|
||||
Task.FromResult(
|
||||
(IReadOnlyList<IElement>?)_pointInTime
|
||||
.Differences
|
||||
.Where(d =>
|
||||
d.Type == DifferenceItemType.Element
|
||||
&& GetParentPath(d.AbsolutePath.Path) == FullName)
|
||||
.Select(MapElement)
|
||||
.ToList()
|
||||
.AsReadOnly()
|
||||
);
|
||||
|
||||
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
var containers = (await GetContainers(token))!;
|
||||
var elements = (await GetElements(token))!;
|
||||
|
||||
return containers.Cast<IItem>().Concat(elements).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
public async Task<bool> IsExists(string name) => (await GetItems())?.Any(i => i.Name == name) ?? false;
|
||||
|
||||
public async Task Refresh() => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
|
||||
public Task Rename(string newName) => Task.CompletedTask;
|
||||
|
||||
private static string GetParentPath(string path) => string.Join(Constants.SeparatorChar, path.Split(Constants.SeparatorChar).Take(-1));
|
||||
|
||||
private IContainer MapContainer(Difference containerDiff)
|
||||
{
|
||||
if (containerDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{nameof(containerDiff)}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Container}.");
|
||||
return new TimeContainer(containerDiff.Name, this, Provider, containerDiff.AbsolutePath.VirtualContentProvider ?? containerDiff.AbsolutePath.ContentProvider, _pointInTime);
|
||||
}
|
||||
|
||||
private IElement MapElement(Difference elementDiff)
|
||||
{
|
||||
if (elementDiff.Type != DifferenceItemType.Container) throw new ArgumentException($"{elementDiff}'s {nameof(Difference.Type)} property is not {DifferenceItemType.Element}.");
|
||||
return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider);
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Core/FileTime.Core/Timeline/TimeElement.cs
Normal file
42
src/Core/FileTime.Core/Timeline/TimeElement.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeElement : IElement
|
||||
{
|
||||
private readonly IContainer _parent;
|
||||
public TimeElement(string name, TimeContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
Name = name;
|
||||
FullName = parent?.FullName == null ? Name : parent.FullName + Constants.SeparatorChar + Name;
|
||||
Provider = contentProvider;
|
||||
VirtualProvider = virtualContentProvider;
|
||||
}
|
||||
|
||||
public bool IsSpecial => false;
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => true;
|
||||
|
||||
public bool CanRename => true;
|
||||
|
||||
public IContentProvider Provider { get; }
|
||||
public IContentProvider VirtualProvider { get; }
|
||||
|
||||
public Task Delete() => Task.CompletedTask;
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
public string GetPrimaryAttributeText() => "";
|
||||
|
||||
public Task Rename(string newName) => Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
89
src/Core/FileTime.Core/Timeline/TimeProvider.cs
Normal file
89
src/Core/FileTime.Core/Timeline/TimeProvider.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeProvider : IContentProvider
|
||||
{
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
public string Name => "time";
|
||||
|
||||
public string? FullName => null;
|
||||
|
||||
public bool IsHidden => false;
|
||||
|
||||
public bool CanDelete => false;
|
||||
|
||||
public bool CanRename => false;
|
||||
|
||||
public IContentProvider Provider => this;
|
||||
|
||||
private readonly PointInTime _pointInTime;
|
||||
|
||||
public TimeProvider(PointInTime pointInTime)
|
||||
{
|
||||
_pointInTime = pointInTime;
|
||||
}
|
||||
|
||||
public bool CanHandlePath(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||
|
||||
public Task<IContainer> CreateContainer(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IElement> CreateElement(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Delete() => throw new NotSupportedException();
|
||||
|
||||
public Task<IItem?> GetByPath(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => null;
|
||||
|
||||
public Task<IReadOnlyList<IContainer>> GetRootContainers(CancellationToken token = default)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task<bool> IsExists(string name)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Task Refresh() => Task.CompletedTask;
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
|
||||
public void SetParent(IContainer container) { }
|
||||
}
|
||||
}
|
||||
217
src/Core/FileTime.Core/Timeline/TimeRunner.cs
Normal file
217
src/Core/FileTime.Core/Timeline/TimeRunner.cs
Normal file
@@ -0,0 +1,217 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Core.Timeline
|
||||
{
|
||||
public class TimeRunner
|
||||
{
|
||||
private readonly CommandExecutor _commandExecutor;
|
||||
private readonly List<ParallelCommands> _commandsToRun = new();
|
||||
private readonly object _guard = new();
|
||||
|
||||
private bool _resourceIsInUse;
|
||||
private readonly List<Thread> _commandRunners = new();
|
||||
private bool _enableRunning = true;
|
||||
|
||||
public bool EnableRunning
|
||||
{
|
||||
get
|
||||
{
|
||||
bool result = true;
|
||||
RunWithLock(() => result = _enableRunning);
|
||||
return result;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
RunWithLock(() => _enableRunning = value);
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ReadOnlyParallelCommands> ParallelCommands { get; private set; } = new List<ReadOnlyParallelCommands>().AsReadOnly();
|
||||
|
||||
public AsyncEventHandler<AbsolutePath> RefreshContainer { get; } = new AsyncEventHandler<AbsolutePath>();
|
||||
|
||||
public event EventHandler? CommandsChanged;
|
||||
|
||||
public TimeRunner(CommandExecutor commandExecutor)
|
||||
{
|
||||
_commandExecutor = commandExecutor;
|
||||
}
|
||||
|
||||
public async Task AddCommand(ICommand command, ParallelCommands? batch = null, bool toNewBatch = false)
|
||||
{
|
||||
await RunWithLockAsync(async () =>
|
||||
{
|
||||
ParallelCommands batchToAdd;
|
||||
|
||||
if (_commandsToRun.Count == 0)
|
||||
{
|
||||
batchToAdd = new ParallelCommands(PointInTime.CreateEmpty());
|
||||
_commandsToRun.Add(batchToAdd);
|
||||
}
|
||||
else if (toNewBatch)
|
||||
{
|
||||
batchToAdd = new ParallelCommands(_commandsToRun.Last().Result);
|
||||
_commandsToRun.Add(batchToAdd);
|
||||
}
|
||||
else if (batch != null && _commandsToRun.Contains(batch))
|
||||
{
|
||||
batchToAdd = batch;
|
||||
}
|
||||
else
|
||||
{
|
||||
batchToAdd = _commandsToRun[0];
|
||||
}
|
||||
await batchToAdd.AddCommand(command);
|
||||
|
||||
await RefreshCommands();
|
||||
|
||||
if (_commandRunners.Count == 0)
|
||||
{
|
||||
StartCommandRunner();
|
||||
}
|
||||
});
|
||||
|
||||
UpdateReadOnlyCommands();
|
||||
}
|
||||
|
||||
public async Task TryStartCommandRunner()
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
if (_commandRunners.Count == 0 && _commandsToRun.Count > 0)
|
||||
{
|
||||
StartCommandRunner();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void StartCommandRunner()
|
||||
{
|
||||
if (_enableRunning)
|
||||
{
|
||||
RunCommands();
|
||||
}
|
||||
}
|
||||
|
||||
private void RunCommands()
|
||||
{
|
||||
if (_commandsToRun.Count > 0)
|
||||
{
|
||||
foreach (var command in _commandsToRun[0].Commands)
|
||||
{
|
||||
if (command.CanRun == CanCommandRun.True || (command.CanRun == CanCommandRun.Forceable && command.ForceRun))
|
||||
{
|
||||
var thread = new Thread(new ParameterizedThreadStart(RunCommand));
|
||||
thread.Start(command);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RunCommand(object? arg)
|
||||
{
|
||||
CommandTimeState? commandToRun = null;
|
||||
try
|
||||
{
|
||||
if (arg is CommandTimeState commandToRun2)
|
||||
{
|
||||
commandToRun = commandToRun2;
|
||||
_commandExecutor.ExecuteCommandAsync(commandToRun.Command, this).Wait();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeCommandThread(Thread.CurrentThread, commandToRun).Wait();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisposeCommandThread(Thread thread, CommandTimeState? command)
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
if (command != null)
|
||||
{
|
||||
_commandsToRun[0].Remove(command);
|
||||
}
|
||||
|
||||
_commandRunners.Remove(thread);
|
||||
});
|
||||
await UpdateReadOnlyCommands();
|
||||
|
||||
await TryStartCommandRunner();
|
||||
}
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
await RunWithLockAsync(async () =>
|
||||
{
|
||||
await RefreshCommands(PointInTime.CreateEmpty());
|
||||
});
|
||||
await UpdateReadOnlyCommands();
|
||||
|
||||
}
|
||||
|
||||
private async Task RefreshCommands(PointInTime? fullStartTime = null)
|
||||
{
|
||||
var curretnTime = fullStartTime ?? _commandsToRun[0].Result;
|
||||
var startIndex = fullStartTime == null ? 1 : 0;
|
||||
|
||||
for (var i = startIndex; i < _commandsToRun.Count; i++)
|
||||
{
|
||||
curretnTime = await _commandsToRun[i].RefreshResult(curretnTime);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateReadOnlyCommands()
|
||||
{
|
||||
await RunWithLockAsync(() =>
|
||||
{
|
||||
ParallelCommands = _commandsToRun.ConvertAll(c => new ReadOnlyParallelCommands(c)).AsReadOnly();
|
||||
});
|
||||
CommandsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task RunWithLockAsync(Action action)
|
||||
{
|
||||
await RunWithLockAsync(() => { action(); return Task.CompletedTask; });
|
||||
}
|
||||
|
||||
private async Task RunWithLockAsync(Func<Task> func)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
lock (_guard)
|
||||
{
|
||||
if (!_resourceIsInUse)
|
||||
{
|
||||
_resourceIsInUse = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(1);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await func();
|
||||
}
|
||||
finally
|
||||
{
|
||||
lock (_guard)
|
||||
{
|
||||
_resourceIsInUse = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RunWithLock(Action action) => RunWithLockAsync(action).Wait();
|
||||
}
|
||||
}
|
||||
@@ -15,12 +15,18 @@
|
||||
<Color x:Key="ItemBackgroundColor">#00000000</Color>
|
||||
<Color x:Key="AlternativeItemBackgroundColor">#10000000</Color>
|
||||
<Color x:Key="SelectedItemBackgroundColor">#93a1a1</Color>
|
||||
<Color x:Key="MarkedItemBackgroundColor">#00000000</Color>
|
||||
<Color x:Key="MarkedAlternativeItemBackgroundColor">#10000000</Color>
|
||||
<Color x:Key="MarkedSelectedItemBackgroundColor">#b58900</Color>
|
||||
|
||||
<Color x:Key="ForegroundColor">#93a1a1</Color>
|
||||
<Color x:Key="AccentForegroundColor">#268bd2</Color>
|
||||
<Color x:Key="LightForegroundColor">#7793a1a1</Color>
|
||||
<Color x:Key="AlternativeForegroundColor">#93a1a1</Color>
|
||||
<Color x:Key="AlternativeItemForegroundColor">#93a1a1</Color>
|
||||
<Color x:Key="SelectedItemForegroundColor">#073642</Color>
|
||||
<Color x:Key="MarkedItemForegroundColor">#b58900</Color>
|
||||
<Color x:Key="MarkedAlternativeItemForegroundColor">#b58900</Color>
|
||||
<Color x:Key="MarkedSelectedItemForegroundColor">#002b36</Color>
|
||||
|
||||
<Color x:Key="ErrorColor">#dc322f</Color>
|
||||
|
||||
@@ -48,6 +54,15 @@
|
||||
<SolidColorBrush
|
||||
x:Key="SelectedItemBackgroundBrush"
|
||||
Color="{DynamicResource SelectedItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedItemBackgroundBrush"
|
||||
Color="{DynamicResource MarkedItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedSelectedItemBackgroundBrush"
|
||||
Color="{DynamicResource MarkedSelectedItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedAlternativeItemBackgroundBrush"
|
||||
Color="{DynamicResource MarkedAlternativeItemBackgroundColor}" />
|
||||
|
||||
<SolidColorBrush
|
||||
x:Key="ForegroundBrush"
|
||||
@@ -59,11 +74,20 @@
|
||||
x:Key="LightForegroundBrush"
|
||||
Color="{DynamicResource LightForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="AlternativeForegroundBrush"
|
||||
Color="{DynamicResource AlternativeForegroundColor}" />
|
||||
x:Key="AlternativeItemForegroundBrush"
|
||||
Color="{DynamicResource AlternativeItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="SelectedItemForegroundBrush"
|
||||
Color="{DynamicResource SelectedItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedItemForegroundBrush"
|
||||
Color="{DynamicResource MarkedItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedAlternativeItemForegroundBrush"
|
||||
Color="{DynamicResource MarkedAlternativeItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedSelectedItemForegroundBrush"
|
||||
Color="{DynamicResource MarkedSelectedItemForegroundColor}" />
|
||||
|
||||
<SolidColorBrush
|
||||
x:Key="ErrorBrush"
|
||||
@@ -79,14 +103,23 @@
|
||||
<converters:ItemViewModeToBrushConverter
|
||||
x:Key="ItemViewModeToForegroundConverter"
|
||||
DefaultBrush="{StaticResource ForegroundBrush}"
|
||||
AlternativeBrush="{StaticResource AlternativeForegroundBrush}"
|
||||
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"/>
|
||||
AlternativeBrush="{StaticResource AlternativeItemForegroundBrush}"
|
||||
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"
|
||||
MarkedBrush="{StaticResource MarkedItemForegroundBrush}"
|
||||
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemForegroundBrush}"
|
||||
MarkedSelectedBrush="{StaticResource MarkedSelectedItemForegroundBrush}"/>
|
||||
<converters:ItemViewModeToBrushConverter
|
||||
x:Key="ItemViewModeToBackgroundConverter"
|
||||
DefaultBrush="{StaticResource ItemBackgroundBrush}"
|
||||
AlternativeBrush="{StaticResource AlternativeItemBackgroundBrush}"
|
||||
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"/>
|
||||
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"
|
||||
MarkedBrush="{StaticResource MarkedItemBackgroundBrush}"
|
||||
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemBackgroundBrush}"
|
||||
MarkedSelectedBrush="{StaticResource MarkedSelectedItemBackgroundBrush}"/>
|
||||
<converters:ContextMenuGenerator x:Key="ContextMenuGenerator"/>
|
||||
<converters:ItemToImageConverter x:Key="ItemToImageConverter"/>
|
||||
<converters:IsNullConverter x:Key="IsNullConverter"/>
|
||||
<converters:IsNullConverter x:Key="IsNotNullConverter" Inverse="true"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace FileTime.Avalonia
|
||||
.RegisterDefaultServices()
|
||||
.AddViewModels()
|
||||
.AddServices()
|
||||
.RegisterCommandHandlers()
|
||||
.BuildServiceProvider();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using FileTime.Core.Components;
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using MvvmGen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using FileTime.App.Core.Tab;
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
@@ -11,7 +13,7 @@ namespace FileTime.Avalonia.Application
|
||||
public partial class AppState
|
||||
{
|
||||
[Property]
|
||||
private ObservableCollection<TabContainer> _tabs = new ObservableCollection<TabContainer>();
|
||||
private ObservableCollection<TabContainer> _tabs = new();
|
||||
|
||||
[Property]
|
||||
private TabContainer _selectedTab;
|
||||
@@ -24,7 +26,66 @@ namespace FileTime.Avalonia.Application
|
||||
|
||||
partial void OnInitialize()
|
||||
{
|
||||
_tabs.CollectionChanged += (o, e) => SelectedTab ??= Tabs.Count > 0 ? Tabs[0] : null;
|
||||
_tabs.CollectionChanged += TabsChanged;
|
||||
}
|
||||
|
||||
private void TabsChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
SelectedTab ??= Tabs.Count > 0 ? Tabs[0] : null;
|
||||
|
||||
List<TabContainer> itemsAdded = new();
|
||||
List<TabContainer> itemsRemoved = new();
|
||||
if (e.NewItems != null && e.OldItems != null)
|
||||
{
|
||||
itemsAdded.AddRange(e.NewItems.Cast<TabContainer>().Except(e.OldItems.Cast<TabContainer>()));
|
||||
itemsRemoved.AddRange(e.OldItems.Cast<TabContainer>().Except(e.NewItems.Cast<TabContainer>()));
|
||||
}
|
||||
else if (e.NewItems != null)
|
||||
{
|
||||
itemsAdded.AddRange(e.NewItems.Cast<TabContainer>());
|
||||
}
|
||||
else if (e.OldItems != null)
|
||||
{
|
||||
itemsRemoved.AddRange(e.OldItems.Cast<TabContainer>());
|
||||
}
|
||||
|
||||
foreach (var item in itemsAdded)
|
||||
{
|
||||
item.TabState.ItemMarked.Add(TabItemMarked);
|
||||
item.TabState.ItemUnmarked.Add(TabItemUnmarked);
|
||||
}
|
||||
|
||||
foreach (var item in itemsRemoved)
|
||||
{
|
||||
item.TabState.ItemMarked.Remove(TabItemMarked);
|
||||
item.TabState.ItemUnmarked.Remove(TabItemUnmarked);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TabItemMarked(TabState tabState, AbsolutePath item)
|
||||
{
|
||||
var tabContainer = Tabs.FirstOrDefault(t => t.TabState == tabState);
|
||||
if (tabContainer != null)
|
||||
{
|
||||
var item2 = (await tabContainer.CurrentLocation.GetItems()).FirstOrDefault(i => i.Item.FullName == item.Path);
|
||||
if (item2 != null)
|
||||
{
|
||||
item2.IsMarked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TabItemUnmarked(TabState tabState, AbsolutePath item)
|
||||
{
|
||||
var tabContainer = Tabs.FirstOrDefault(t => t.TabState == tabState);
|
||||
if (tabContainer != null)
|
||||
{
|
||||
var item2 = (await tabContainer.CurrentLocation.GetItems()).FirstOrDefault(i => i.Item.FullName == item.Path);
|
||||
if (item2 != null)
|
||||
{
|
||||
item2.IsMarked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
public interface INewItemProcessor
|
||||
{
|
||||
Task UpdateMarkedItems(ContainerViewModel containerViewModel);
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,11 @@ using FileTime.Avalonia.Services;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.App.Core.Tab;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
@@ -18,8 +18,11 @@ namespace FileTime.Avalonia.Application
|
||||
[Inject(typeof(ItemNameConverterService))]
|
||||
[Inject(typeof(LocalContentProvider))]
|
||||
[Inject(typeof(Tab))]
|
||||
public partial class TabContainer
|
||||
public partial class TabContainer : INewItemProcessor
|
||||
{
|
||||
[Property]
|
||||
private TabState _tabState;
|
||||
|
||||
[Property]
|
||||
private ContainerViewModel _parent;
|
||||
|
||||
@@ -45,25 +48,33 @@ namespace FileTime.Avalonia.Application
|
||||
if (_selectedItem != value)// && value != null
|
||||
{
|
||||
_selectedItem = value;
|
||||
|
||||
OnPropertyChanged("SelectedItem");
|
||||
SelectedItemChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnInitialize()
|
||||
{
|
||||
_tabState = new TabState(Tab);
|
||||
}
|
||||
|
||||
public async Task Init(int tabNumber)
|
||||
{
|
||||
TabNumber = tabNumber;
|
||||
Tab.CurrentLocationChanged.Add(Tab_CurrentLocationChanged);
|
||||
Tab.CurrentSelectedItemChanged.Add(Tab_CurrentSelectedItemChanged);
|
||||
|
||||
CurrentLocation = new ContainerViewModel(await Tab.GetCurrentLocation(), ItemNameConverterService);
|
||||
var currentLocation = await Tab.GetCurrentLocation();
|
||||
var parent = GenerateParent(currentLocation);
|
||||
CurrentLocation = new ContainerViewModel(this, parent, currentLocation, ItemNameConverterService);
|
||||
await CurrentLocation.Init();
|
||||
|
||||
var parent = (await Tab.GetCurrentLocation()).GetParent();
|
||||
if (parent != null)
|
||||
{
|
||||
Parent = new ContainerViewModel(parent, ItemNameConverterService);
|
||||
parent.ChildrenToAdopt.Add(CurrentLocation);
|
||||
Parent = parent;
|
||||
await Parent.Init();
|
||||
}
|
||||
else
|
||||
@@ -74,16 +85,28 @@ namespace FileTime.Avalonia.Application
|
||||
await UpdateCurrentSelectedItem();
|
||||
}
|
||||
|
||||
private ContainerViewModel? GenerateParent(IContainer? container, bool recursive = false)
|
||||
{
|
||||
var parentContainer = container?.GetParent();
|
||||
if (parentContainer == null) return null;
|
||||
var parentParent = recursive ? GenerateParent(parentContainer.GetParent(), recursive) : null;
|
||||
|
||||
var parent = new ContainerViewModel(this, parentParent, parentContainer, ItemNameConverterService);
|
||||
parentParent?.ChildrenToAdopt.Add(parent);
|
||||
return parent;
|
||||
}
|
||||
|
||||
private async Task Tab_CurrentLocationChanged(object? sender, AsyncEventArgs e)
|
||||
{
|
||||
var currentLocation = await Tab.GetCurrentLocation();
|
||||
CurrentLocation = new ContainerViewModel(currentLocation, ItemNameConverterService);
|
||||
var parent = GenerateParent(currentLocation);
|
||||
CurrentLocation = new ContainerViewModel(this, parent, currentLocation, ItemNameConverterService);
|
||||
await CurrentLocation.Init();
|
||||
|
||||
var parent = currentLocation.GetParent();
|
||||
if (parent != null)
|
||||
{
|
||||
Parent = new ContainerViewModel(parent, ItemNameConverterService);
|
||||
parent.ChildrenToAdopt.Add(CurrentLocation);
|
||||
Parent = parent;
|
||||
await Parent.Init();
|
||||
}
|
||||
else
|
||||
@@ -97,39 +120,89 @@ namespace FileTime.Avalonia.Application
|
||||
await UpdateCurrentSelectedItem();
|
||||
}
|
||||
|
||||
private async Task UpdateCurrentSelectedItem()
|
||||
public async Task UpdateCurrentSelectedItem()
|
||||
{
|
||||
var tabCurrentSelectenItem = await Tab.GetCurrentSelectedItem();
|
||||
IItemViewModel? currentSelectenItem = null;
|
||||
if (tabCurrentSelectenItem == null)
|
||||
try
|
||||
{
|
||||
SelectedItem = null;
|
||||
ChildContainer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSelectenItem = (await _currentLocation.GetItems()).FirstOrDefault(i => i.Item.Name == tabCurrentSelectenItem.Name);
|
||||
if (currentSelectenItem is ContainerViewModel currentSelectedContainer)
|
||||
{
|
||||
SelectedItem = currentSelectedContainer;
|
||||
ChildContainer = currentSelectedContainer;
|
||||
}
|
||||
else if (currentSelectenItem is ElementViewModel element)
|
||||
{
|
||||
SelectedItem = element;
|
||||
ChildContainer = null;
|
||||
}
|
||||
else
|
||||
var tabCurrentSelectenItem = await Tab.GetCurrentSelectedItem();
|
||||
IItemViewModel? currentSelectenItem = null;
|
||||
if (tabCurrentSelectenItem == null)
|
||||
{
|
||||
SelectedItem = null;
|
||||
ChildContainer = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSelectenItem = (await _currentLocation.GetItems()).FirstOrDefault(i => i.Item.Name == tabCurrentSelectenItem.Name);
|
||||
if (currentSelectenItem is ContainerViewModel currentSelectedContainer)
|
||||
{
|
||||
SelectedItem = currentSelectedContainer;
|
||||
ChildContainer = currentSelectedContainer;
|
||||
}
|
||||
else if (currentSelectenItem is ElementViewModel element)
|
||||
{
|
||||
SelectedItem = element;
|
||||
ChildContainer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedItem = null;
|
||||
ChildContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
var items = await _currentLocation.GetItems();
|
||||
foreach (var item in items)
|
||||
var items = await _currentLocation.GetItems();
|
||||
if (items != null && items.Count > 0)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
var isSelected = item == currentSelectenItem;
|
||||
item.IsSelected = isSelected;
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
var parent = item.Parent;
|
||||
while (parent != null)
|
||||
{
|
||||
parent.IsSelected = true;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var child = item;
|
||||
while (child is ContainerViewModel containerViewModel && containerViewModel.Container.IsLoaded)
|
||||
{
|
||||
var activeChildItem = await Tab.GetItemByLastPath(containerViewModel.Container);
|
||||
child = (await containerViewModel.GetItems()).FirstOrDefault(i => i.Item == activeChildItem);
|
||||
if (child != null)
|
||||
{
|
||||
child.IsSelected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
//INFO collection modified exception on: child = (await containerViewModel.GetItems()).FirstOrDefault(i => i.Item == activeChildItem);
|
||||
//TODO: handle or error message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var parent = _currentLocation;
|
||||
while (parent != null)
|
||||
{
|
||||
parent.IsSelected = true;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
item.IsSelected = item == currentSelectenItem;
|
||||
//INFO collection modified exception on: currentSelectenItem = (await _currentLocation.GetItems()).FirstOrDefault(i => i.Item.Name == tabCurrentSelectenItem.Name);
|
||||
//TODO: handle or error message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,9 +285,32 @@ namespace FileTime.Avalonia.Application
|
||||
(await Tab.GetCurrentLocation())?.CreateContainer(name);
|
||||
}
|
||||
|
||||
public async Task CreateElement(string name)
|
||||
{
|
||||
(await Tab.GetCurrentLocation())?.CreateElement(name);
|
||||
}
|
||||
|
||||
public async Task OpenContainer(IContainer container)
|
||||
{
|
||||
await Tab.OpenContainer(container);
|
||||
}
|
||||
|
||||
public async Task MarkCurrentItem()
|
||||
{
|
||||
await _tabState.MakrCurrentItem();
|
||||
}
|
||||
|
||||
public async Task UpdateMarkedItems(ContainerViewModel containerViewModel)
|
||||
{
|
||||
if (containerViewModel == CurrentLocation && containerViewModel.Container.IsLoaded)
|
||||
{
|
||||
var selectedItems = TabState.GetCurrentMarkedItems(containerViewModel.Container);
|
||||
|
||||
foreach (var item in await containerViewModel.GetItems())
|
||||
{
|
||||
item.IsMarked = selectedItems.Any(c => c.Path == item.Item.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
src/GuiApp/FileTime.Avalonia/Converters/IsNullConverter.cs
Normal file
23
src/GuiApp/FileTime.Avalonia/Converters/IsNullConverter.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class IsNullConverter : IValueConverter
|
||||
{
|
||||
public bool Inverse { get; set; }
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
var result = value == null;
|
||||
if (Inverse) result = !result;
|
||||
return result;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Svg.Skia;
|
||||
using FileTime.Avalonia.IconProviders;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class ItemToImageConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value == null) return null;
|
||||
|
||||
IIconProvider converter = new MaterialIconProvider();
|
||||
|
||||
IItem item = value switch
|
||||
{
|
||||
ContainerViewModel container => container.Container,
|
||||
ElementViewModel element => element.Element,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
var path = converter.GetImage(item)!;
|
||||
var source = SvgSource.Load<SvgSource>("avares://FileTime.Avalonia" + path, null);
|
||||
return new SvgImage { Source = source };
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ namespace FileTime.Avalonia.Converters
|
||||
public Brush? DefaultBrush { get; set; }
|
||||
public Brush? AlternativeBrush { get; set; }
|
||||
public Brush? SelectedBrush { get; set; }
|
||||
public Brush? MarkedBrush { get; set; }
|
||||
public Brush? MarkedSelectedBrush { get; set; }
|
||||
public Brush? MarkedAlternativeBrush { get; set; }
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
@@ -21,6 +24,9 @@ namespace FileTime.Avalonia.Converters
|
||||
ItemViewMode.Default => DefaultBrush,
|
||||
ItemViewMode.Alternative => AlternativeBrush,
|
||||
ItemViewMode.Selected => SelectedBrush,
|
||||
ItemViewMode.Marked => MarkedBrush,
|
||||
ItemViewMode.MarkedSelected => MarkedSelectedBrush,
|
||||
ItemViewMode.MarkedAlternative => MarkedAlternativeBrush,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.IconProviders
|
||||
{
|
||||
public interface IIconProvider
|
||||
{
|
||||
string GetImage(IItem item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Providers.Local;
|
||||
using System.Linq;
|
||||
|
||||
namespace FileTime.Avalonia.IconProviders
|
||||
{
|
||||
public class MaterialIconProvider : IIconProvider
|
||||
{
|
||||
public string GetImage(IItem item)
|
||||
{
|
||||
var icon = "file.svg";
|
||||
if (item is IContainer)
|
||||
{
|
||||
icon = "folder.svg";
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
if(element is LocalFile localFile && element.FullName.EndsWith(".svg"))
|
||||
{
|
||||
return localFile.File.FullName;
|
||||
}
|
||||
icon = !element.Name.Contains('.')
|
||||
? icon
|
||||
: element.Name.Split('.').Last() switch
|
||||
{
|
||||
"cs" => "csharp.svg",
|
||||
_ => icon
|
||||
};
|
||||
}
|
||||
return "/Assets/material/" + icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,10 @@ namespace FileTime.Avalonia.Misc
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public InputElementWrapper(InputElement inputElement)
|
||||
public InputElementWrapper(InputElement inputElement, string? defaultValue = null)
|
||||
{
|
||||
InputElement = inputElement;
|
||||
Value = defaultValue ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using FileTime.Avalonia.Application;
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FileTime.Avalonia.Services
|
||||
@@ -20,9 +21,8 @@ namespace FileTime.Avalonia.Services
|
||||
{
|
||||
var nameLeft = itemViewModel.Item.Name;
|
||||
|
||||
while (nameLeft.ToLower().Contains(rapidTravelText))
|
||||
while (nameLeft.ToLower().IndexOf(rapidTravelText, StringComparison.Ordinal) is int rapidTextStart && rapidTextStart != -1)
|
||||
{
|
||||
var rapidTextStart = nameLeft.ToLower().IndexOf(rapidTravelText);
|
||||
var before = rapidTextStart > 0 ? nameLeft.Substring(0, rapidTextStart) : null;
|
||||
var rapidTravel = nameLeft.Substring(rapidTextStart, rapidTravelText.Length);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using FileTime.Avalonia.Application;
|
||||
using FileTime.Avalonia.Services;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Core.Command;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Avalonia
|
||||
@@ -29,6 +30,15 @@ namespace FileTime.Avalonia
|
||||
throw new System.Exception("TODO: implement linux contextmenu provider");
|
||||
}
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
internal static IServiceCollection RegisterCommandHandlers(this IServiceCollection serviceCollection)
|
||||
{
|
||||
foreach (var commandHandler in FileTime.Providers.Local.Startup.GetCommandHandlers())
|
||||
{
|
||||
serviceCollection.AddTransient(typeof(ICommandHandler), commandHandler);
|
||||
}
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Avalonia.Application;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -16,8 +17,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Inject(typeof(ItemNameConverterService))]
|
||||
public partial class ContainerViewModel : IItemViewModel
|
||||
{
|
||||
private bool isRefreshing;
|
||||
private bool isInitialized;
|
||||
private bool _isRefreshing;
|
||||
private bool _isInitialized;
|
||||
private INewItemProcessor _newItemProcessor;
|
||||
|
||||
[Property]
|
||||
private IContainer _container;
|
||||
@@ -25,29 +27,39 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private bool _isSelected;
|
||||
|
||||
public IItem Item => _container;
|
||||
|
||||
//[Property]
|
||||
private readonly ObservableCollection<ContainerViewModel> _containers = new ObservableCollection<ContainerViewModel>();
|
||||
|
||||
//[Property]
|
||||
private readonly ObservableCollection<ElementViewModel> _elements = new ObservableCollection<ElementViewModel>();
|
||||
|
||||
//[Property]
|
||||
private readonly ObservableCollection<IItemViewModel> _items = new ObservableCollection<IItemViewModel>();
|
||||
|
||||
[Property]
|
||||
private bool _isAlternative;
|
||||
|
||||
[Property]
|
||||
private bool _isMarked;
|
||||
|
||||
[Property]
|
||||
private ContainerViewModel? _parent;
|
||||
|
||||
public IItem Item => _container;
|
||||
|
||||
private readonly ObservableCollection<ContainerViewModel> _containers = new ObservableCollection<ContainerViewModel>();
|
||||
|
||||
private readonly ObservableCollection<ElementViewModel> _elements = new ObservableCollection<ElementViewModel>();
|
||||
|
||||
private readonly ObservableCollection<IItemViewModel> _items = new ObservableCollection<IItemViewModel>();
|
||||
|
||||
public List<IItemViewModel> ChildrenToAdopt { get; } = new List<IItemViewModel>();
|
||||
|
||||
|
||||
[PropertyInvalidate(nameof(IsSelected))]
|
||||
[PropertyInvalidate(nameof(IsAlternative))]
|
||||
[PropertyInvalidate(nameof(IsMarked))]
|
||||
public ItemViewMode ViewMode =>
|
||||
IsSelected
|
||||
? ItemViewMode.Selected
|
||||
: IsAlternative
|
||||
? ItemViewMode.Alternative
|
||||
: ItemViewMode.Default;
|
||||
(IsMarked, IsSelected, IsAlternative) switch
|
||||
{
|
||||
(true, true, _) => ItemViewMode.MarkedSelected,
|
||||
(true, false, true) => ItemViewMode.MarkedAlternative,
|
||||
(false, true, _) => ItemViewMode.Selected,
|
||||
(false, false, true) => ItemViewMode.Alternative,
|
||||
(true, false, false) => ItemViewMode.Marked,
|
||||
_ => ItemViewMode.Default
|
||||
};
|
||||
|
||||
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
|
||||
|
||||
@@ -56,7 +68,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!isInitialized) Task.Run(Refresh);
|
||||
if (!_isInitialized) Task.Run(Refresh);
|
||||
return _containers;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +78,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!isInitialized) Task.Run(Refresh);
|
||||
if (!_isInitialized) Task.Run(Refresh);
|
||||
return _elements;
|
||||
}
|
||||
}
|
||||
@@ -76,13 +88,16 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!isInitialized) Task.Run(Refresh);
|
||||
if (!_isInitialized) Task.Run(Refresh);
|
||||
return _items;
|
||||
}
|
||||
}
|
||||
|
||||
public ContainerViewModel(IContainer container, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
||||
public ContainerViewModel(INewItemProcessor newItemProcessor, ContainerViewModel? parent, IContainer container, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
||||
{
|
||||
_newItemProcessor = newItemProcessor;
|
||||
Parent = parent;
|
||||
|
||||
Container = container;
|
||||
Container.Refreshed.Add(Container_Refreshed);
|
||||
}
|
||||
@@ -94,7 +109,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
await Refresh(initializeChildren);
|
||||
}
|
||||
|
||||
private async Task Container_Refreshed(object sender, AsyncEventArgs e)
|
||||
private async Task Container_Refreshed(object? sender, AsyncEventArgs e)
|
||||
{
|
||||
await Refresh(false);
|
||||
}
|
||||
@@ -105,16 +120,16 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
private async Task Refresh(bool initializeChildren)
|
||||
{
|
||||
if (isRefreshing) return;
|
||||
if (_isRefreshing) return;
|
||||
|
||||
isInitialized = true;
|
||||
_isInitialized = true;
|
||||
|
||||
try
|
||||
{
|
||||
isRefreshing = true;
|
||||
_isRefreshing = true;
|
||||
|
||||
var containers = (await _container.GetContainers()).Select(c => new ContainerViewModel(c, ItemNameConverterService)).ToList();
|
||||
var elements = (await _container.GetElements()).Select(e => new ElementViewModel(e, ItemNameConverterService)).ToList();
|
||||
var containers = (await _container.GetContainers()).Select(c => AdoptOrCreateItem(c, (c2) => new ContainerViewModel(_newItemProcessor, this, c2, ItemNameConverterService))).ToList();
|
||||
var elements = (await _container.GetElements()).Select(e => AdoptOrCreateItem(e, (e2) => new ElementViewModel(e2, this, ItemNameConverterService))).ToList();
|
||||
|
||||
_containers.Clear();
|
||||
_elements.Clear();
|
||||
@@ -141,24 +156,51 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
catch { }
|
||||
|
||||
isRefreshing = false;
|
||||
await _newItemProcessor.UpdateMarkedItems(this);
|
||||
|
||||
_isRefreshing = false;
|
||||
}
|
||||
|
||||
private TResult AdoptOrCreateItem<T, TResult>(T item, Func<T, TResult> generator) where T : IItem
|
||||
{
|
||||
var itemToAdopt = ChildrenToAdopt.Find(i => i.Item.Name == item.Name);
|
||||
if (itemToAdopt is TResult itemViewModel) return itemViewModel;
|
||||
|
||||
return generator(item);
|
||||
}
|
||||
|
||||
public void Unload(bool recursive = true)
|
||||
{
|
||||
_isInitialized = false;
|
||||
if (recursive)
|
||||
{
|
||||
foreach (var container in _containers)
|
||||
{
|
||||
container.Unload(true);
|
||||
container.ChildrenToAdopt.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
_containers.Clear();
|
||||
_elements.Clear();
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<ContainerViewModel>> GetContainers()
|
||||
{
|
||||
if (!isInitialized) await Task.Run(Refresh);
|
||||
if (!_isInitialized) await Task.Run(Refresh);
|
||||
return _containers;
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<ElementViewModel>> GetElements()
|
||||
{
|
||||
if (!isInitialized) await Task.Run(Refresh);
|
||||
if (!_isInitialized) await Task.Run(Refresh);
|
||||
return _elements;
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<IItemViewModel>> GetItems()
|
||||
{
|
||||
if (!isInitialized) await Task.Run(Refresh);
|
||||
if (!_isInitialized) await Task.Run(Refresh);
|
||||
return _items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,20 +21,32 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private bool _isAlternative;
|
||||
|
||||
[Property]
|
||||
private bool _isMarked;
|
||||
|
||||
[Property]
|
||||
private ContainerViewModel? _parent;
|
||||
|
||||
[PropertyInvalidate(nameof(IsSelected))]
|
||||
[PropertyInvalidate(nameof(IsAlternative))]
|
||||
[PropertyInvalidate(nameof(IsMarked))]
|
||||
public ItemViewMode ViewMode =>
|
||||
IsSelected
|
||||
? ItemViewMode.Selected
|
||||
: IsAlternative
|
||||
? ItemViewMode.Alternative
|
||||
: ItemViewMode.Default;
|
||||
(IsMarked, IsSelected, IsAlternative) switch
|
||||
{
|
||||
(true, true, _) => ItemViewMode.MarkedSelected,
|
||||
(true, false, true) => ItemViewMode.MarkedAlternative,
|
||||
(false, true, _) => ItemViewMode.Selected,
|
||||
(false, false, true) => ItemViewMode.Alternative,
|
||||
(true, false, false) => ItemViewMode.Marked,
|
||||
_ => ItemViewMode.Default
|
||||
};
|
||||
|
||||
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
|
||||
|
||||
public ElementViewModel(IElement element, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
||||
public ElementViewModel(IElement element, ContainerViewModel parent, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
||||
{
|
||||
Element = element;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public void InvalidateDisplayName() => OnPropertyChanged(nameof(DisplayName));
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Avalonia.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -12,6 +10,8 @@ namespace FileTime.Avalonia.ViewModels
|
||||
bool IsSelected { get; set; }
|
||||
|
||||
bool IsAlternative { get; set; }
|
||||
bool IsMarked { get; set; }
|
||||
ContainerViewModel? Parent{ get; set; }
|
||||
|
||||
ItemViewMode ViewMode { get; }
|
||||
|
||||
|
||||
@@ -6,8 +6,11 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
public enum ItemViewMode
|
||||
{
|
||||
Selected,
|
||||
Default,
|
||||
Alternative,
|
||||
Default
|
||||
Selected,
|
||||
Marked,
|
||||
MarkedSelected,
|
||||
MarkedAlternative
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Input;
|
||||
using FileTime.App.Core.Clipboard;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -34,6 +38,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
private List<CommandBinding> _commandBindings = new();
|
||||
private List<CommandBinding> _universalCommandBindings = new();
|
||||
|
||||
private IClipboard _clipboard;
|
||||
private TimeRunner _timeRunner;
|
||||
|
||||
private Action? _inputHandler;
|
||||
|
||||
[Property]
|
||||
@@ -51,10 +58,16 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private List<RootDriveInfo> _rootDriveInfos;
|
||||
|
||||
public Action? FocusDefaultElement { get; set; }
|
||||
[Property]
|
||||
private string _messageBoxText;
|
||||
|
||||
public IReadOnlyList<ReadOnlyParallelCommands> TimelineCommands => _timeRunner.ParallelCommands;
|
||||
|
||||
async partial void OnInitialize()
|
||||
{
|
||||
_clipboard = App.ServiceProvider.GetService<IClipboard>()!;
|
||||
_timeRunner = App.ServiceProvider.GetService<TimeRunner>()!;
|
||||
_timeRunner.CommandsChanged += (o, e) => OnPropertyChanged(nameof(TimelineCommands));
|
||||
InitCommandBindings();
|
||||
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.Up) });
|
||||
@@ -62,6 +75,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.Tab) });
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.PageDown) });
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.PageUp) });
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.F4, alt: true) });
|
||||
|
||||
var tab = new Tab();
|
||||
await tab.Init(LocalContentProvider);
|
||||
@@ -176,7 +190,6 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
_previousKeys.Clear();
|
||||
PossibleCommands = new();
|
||||
FocusDefaultElement?.Invoke();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -190,7 +203,6 @@ namespace FileTime.Avalonia.ViewModels
|
||||
AppState.RapidTravelText = "";
|
||||
|
||||
await AppState.SelectedTab.OpenContainer(await AppState.SelectedTab.CurrentLocation.Container.WithoutVirtualContainer(RAPIDTRAVEL));
|
||||
FocusDefaultElement?.Invoke();
|
||||
}
|
||||
|
||||
public async Task SwitchToTab(int number)
|
||||
@@ -260,7 +272,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
if (Inputs != null)
|
||||
{
|
||||
AppState.SelectedTab.CreateContainer(Inputs[0].Value).Wait();
|
||||
var container = AppState.SelectedTab.CurrentLocation.Container;
|
||||
var createContainerCommand = new CreateContainerCommand(new Core.Models.AbsolutePath(container), Inputs[0].Value);
|
||||
_timeRunner.AddCommand(createContainerCommand).Wait();
|
||||
Inputs = null;
|
||||
}
|
||||
};
|
||||
@@ -270,10 +284,222 @@ namespace FileTime.Avalonia.ViewModels
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CreateElement()
|
||||
{
|
||||
var handler = () =>
|
||||
{
|
||||
if (Inputs != null)
|
||||
{
|
||||
var container = AppState.SelectedTab.CurrentLocation.Container;
|
||||
var createElementCommand = new CreateElementCommand(new Core.Models.AbsolutePath(container), Inputs[0].Value);
|
||||
_timeRunner.AddCommand(createElementCommand).Wait();
|
||||
Inputs = null;
|
||||
}
|
||||
};
|
||||
|
||||
ReadInputs(new List<Core.Interactions.InputElement>() { new Core.Interactions.InputElement("Element name", InputType.Text) }, handler);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task MarkCurrentItem()
|
||||
{
|
||||
await AppState.SelectedTab.MarkCurrentItem();
|
||||
}
|
||||
|
||||
public 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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentSelectedItem = AppState.SelectedTab.SelectedItem?.Item;
|
||||
if (currentSelectedItem != null)
|
||||
{
|
||||
_clipboard.AddContent(new AbsolutePath(currentSelectedItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task Cut()
|
||||
{
|
||||
_clipboard.Clear();
|
||||
_clipboard.SetCommand<MoveCommand>();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task Delete()
|
||||
{
|
||||
IList<Core.Models.AbsolutePath>? itemsToDelete = null;
|
||||
var askForDelete = false;
|
||||
var questionText = "";
|
||||
var shouldDelete = false;
|
||||
|
||||
var currentSelectedItems = await AppState.SelectedTab.TabState.GetCurrentMarkedItems();
|
||||
var currentSelectedItem = AppState.SelectedTab.SelectedItem?.Item;
|
||||
if (currentSelectedItems.Count > 0)
|
||||
{
|
||||
itemsToDelete = currentSelectedItems.Cast<Core.Models.AbsolutePath>().ToList();
|
||||
|
||||
//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<Core.Models.AbsolutePath>()
|
||||
{
|
||||
new Core.Models.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)
|
||||
{
|
||||
ShowMessageBox(questionText, HandleDelete);
|
||||
}
|
||||
else if (shouldDelete)
|
||||
{
|
||||
HandleDelete();
|
||||
}
|
||||
}
|
||||
|
||||
void HandleDelete()
|
||||
{
|
||||
var deleteCommand = new DeleteCommand();
|
||||
|
||||
foreach (var itemToDelete in itemsToDelete!)
|
||||
{
|
||||
deleteCommand.ItemsToDelete.Add(itemToDelete);
|
||||
}
|
||||
|
||||
_timeRunner.AddCommand(deleteCommand).Wait();
|
||||
_clipboard.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PasteMerge()
|
||||
{
|
||||
await Paste(TransportMode.Merge);
|
||||
}
|
||||
public async Task PasteOverwrite()
|
||||
{
|
||||
await Paste(TransportMode.Overwrite);
|
||||
}
|
||||
|
||||
public 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 _timeRunner.AddCommand(command);
|
||||
|
||||
_clipboard.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private Task Rename()
|
||||
{
|
||||
var selectedItem = AppState.SelectedTab.SelectedItem?.Item;
|
||||
if (selectedItem != null)
|
||||
{
|
||||
var handler = () =>
|
||||
{
|
||||
if (Inputs != null)
|
||||
{
|
||||
var renameCommand = new RenameCommand(new Core.Models.AbsolutePath(selectedItem), Inputs[0].Value);
|
||||
_timeRunner.AddCommand(renameCommand).Wait();
|
||||
}
|
||||
};
|
||||
|
||||
ReadInputs(new List<Core.Interactions.InputElement>() { new Core.Interactions.InputElement("New name", InputType.Text, selectedItem.Name) }, handler);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RefreshCurrentLocation()
|
||||
{
|
||||
await AppState.SelectedTab.CurrentLocation.Container.Refresh();
|
||||
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();
|
||||
}
|
||||
|
||||
[Command]
|
||||
public void ProcessInputs()
|
||||
{
|
||||
_inputHandler();
|
||||
_inputHandler?.Invoke();
|
||||
|
||||
Inputs = null;
|
||||
_inputHandler = null;
|
||||
@@ -286,8 +512,31 @@ namespace FileTime.Avalonia.ViewModels
|
||||
_inputHandler = null;
|
||||
}
|
||||
|
||||
[Command]
|
||||
public void ProcessMessageBoxCommand()
|
||||
{
|
||||
_inputHandler?.Invoke();
|
||||
|
||||
MessageBoxText = null;
|
||||
_inputHandler = null;
|
||||
}
|
||||
|
||||
[Command]
|
||||
public void CancelMessageBoxCommand()
|
||||
{
|
||||
MessageBoxText = null;
|
||||
_inputHandler = null;
|
||||
}
|
||||
|
||||
public async Task<bool> ProcessKeyDown(Key key, KeyModifiers keyModifiers)
|
||||
{
|
||||
if (key == Key.LeftAlt
|
||||
|| key == Key.RightAlt
|
||||
|| key == Key.LeftShift
|
||||
|| key == Key.RightShift
|
||||
|| key == Key.LeftCtrl
|
||||
|| key == Key.RightCtrl) return false;
|
||||
|
||||
NoCommandFound = false;
|
||||
|
||||
var isAltPressed = (keyModifiers & KeyModifiers.Alt) == KeyModifiers.Alt;
|
||||
@@ -312,13 +561,12 @@ namespace FileTime.Avalonia.ViewModels
|
||||
await selectedCommandBinding.InvokeAsync();
|
||||
_previousKeys.Clear();
|
||||
PossibleCommands = new();
|
||||
|
||||
FocusDefaultElement?.Invoke();
|
||||
}
|
||||
else if (_keysToSkip.Any(k => AreKeysEqual(k, _previousKeys)))
|
||||
{
|
||||
_previousKeys.Clear();
|
||||
PossibleCommands = new();
|
||||
return false;
|
||||
}
|
||||
else if (_previousKeys.Count == 2)
|
||||
{
|
||||
@@ -370,7 +618,11 @@ namespace FileTime.Avalonia.ViewModels
|
||||
if (selectedCommandBinding != null)
|
||||
{
|
||||
await selectedCommandBinding.InvokeAsync();
|
||||
FocusDefaultElement?.Invoke();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,8 +651,6 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
await AppState.SelectedTab.MoveCursorToFirst();
|
||||
}
|
||||
|
||||
FocusDefaultElement?.Invoke();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -415,7 +665,13 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
private void ReadInputs(List<Core.Interactions.InputElement> inputs, Action inputHandler)
|
||||
{
|
||||
Inputs = inputs.Select(i => new InputElementWrapper(i)).ToList();
|
||||
Inputs = inputs.Select(i => new InputElementWrapper(i, i.DefaultValue)).ToList();
|
||||
_inputHandler = inputHandler;
|
||||
}
|
||||
|
||||
private void ShowMessageBox(string text, Action inputHandler)
|
||||
{
|
||||
MessageBoxText = text;
|
||||
_inputHandler = inputHandler;
|
||||
}
|
||||
|
||||
@@ -451,6 +707,11 @@ namespace FileTime.Avalonia.ViewModels
|
||||
FileTime.App.Core.Command.Commands.CreateContainer,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.C),new KeyWithModifiers(Key.C)},
|
||||
CreateContainer),
|
||||
new CommandBinding(
|
||||
"create element",
|
||||
FileTime.App.Core.Command.Commands.CreateElement,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.C),new KeyWithModifiers(Key.E)},
|
||||
CreateElement),
|
||||
new CommandBinding(
|
||||
"move to first",
|
||||
FileTime.App.Core.Command.Commands.MoveToTop,
|
||||
@@ -526,6 +787,66 @@ namespace FileTime.Avalonia.ViewModels
|
||||
FileTime.App.Core.Command.Commands.GoToHome,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.Q)},
|
||||
CloseTab),
|
||||
new CommandBinding(
|
||||
"select",
|
||||
FileTime.App.Core.Command.Commands.Select,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.Space)},
|
||||
MarkCurrentItem),
|
||||
new CommandBinding(
|
||||
"copy",
|
||||
FileTime.App.Core.Command.Commands.Copy,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.Y),new KeyWithModifiers(Key.Y)},
|
||||
Copy),
|
||||
new CommandBinding(
|
||||
"cut",
|
||||
FileTime.App.Core.Command.Commands.Cut,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.D),new KeyWithModifiers(Key.D)},
|
||||
Cut),
|
||||
new CommandBinding(
|
||||
"delete",
|
||||
FileTime.App.Core.Command.Commands.Delete,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.D),new KeyWithModifiers(Key.D, shift: true)},
|
||||
Delete),
|
||||
new CommandBinding(
|
||||
"paste merge",
|
||||
FileTime.App.Core.Command.Commands.PasteMerge,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.P),new KeyWithModifiers(Key.P)},
|
||||
PasteMerge),
|
||||
new CommandBinding(
|
||||
"paste (overwrite)",
|
||||
FileTime.App.Core.Command.Commands.PasteOverwrite,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.P),new KeyWithModifiers(Key.O)},
|
||||
PasteOverwrite),
|
||||
new CommandBinding(
|
||||
"paste (skip)",
|
||||
FileTime.App.Core.Command.Commands.PasteSkip,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.P),new KeyWithModifiers(Key.S)},
|
||||
PasteSkip),
|
||||
new CommandBinding(
|
||||
"rename",
|
||||
FileTime.App.Core.Command.Commands.Rename,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.C),new KeyWithModifiers(Key.W)},
|
||||
Rename),
|
||||
new CommandBinding(
|
||||
"timeline pause",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.T),new KeyWithModifiers(Key.P)},
|
||||
PauseTimeline),
|
||||
new CommandBinding(
|
||||
"timeline start",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.T),new KeyWithModifiers(Key.S)},
|
||||
ContinueTimeline),
|
||||
new CommandBinding(
|
||||
"refresh timeline",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.T),new KeyWithModifiers(Key.R)},
|
||||
RefreshTimeline),
|
||||
new CommandBinding(
|
||||
"refresh",
|
||||
FileTime.App.Core.Command.Commands.Refresh,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.R)},
|
||||
RefreshCurrentLocation),
|
||||
};
|
||||
var universalCommandBindings = new List<CommandBinding>()
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
Height="18"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Source="{SvgImage /Assets/material/folder.svg}" />
|
||||
Source="{Binding Converter={StaticResource ItemToImageConverter}}" />
|
||||
|
||||
<ItemsControl
|
||||
Grid.Column="1"
|
||||
|
||||
@@ -89,8 +89,29 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="1" RowDefinitions="40,*,Auto">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Grid Grid.Column="2" RowDefinitions="Auto,40,*,Auto">
|
||||
<Grid>
|
||||
<ItemsControl Items="{Binding TimelineCommands}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl Items="{Binding Commands}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" ColumnDefinitions="*,Auto">
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
@@ -98,7 +119,7 @@
|
||||
Text="{Binding AppState.SelectedTab.TabNumber,StringFormat=({0})}" />
|
||||
|
||||
<local:PathPresenter Margin="10,5,0,5" DataContext="{Binding AppState.SelectedTab.CurrentLocation.Container.FullName}"/>
|
||||
|
||||
|
||||
<TextBlock
|
||||
Margin="0,5,10,5"
|
||||
Text="{Binding AppState.SelectedTab.SelectedItem.Item.Name}" Foreground="{StaticResource AccentForegroundBrush}" />
|
||||
@@ -123,7 +144,7 @@
|
||||
|
||||
<local:PathPresenter Margin="5,0,0,0" DataContext="{Binding CurrentLocation.Container.FullName}"/>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<Rectangle Fill="{DynamicResource ForegroundBrush}" Grid.Row="1" IsVisible="{Binding IsSelected}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
@@ -132,7 +153,7 @@
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Row="2"
|
||||
Margin="20,0,0,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -146,8 +167,8 @@
|
||||
<Grid>
|
||||
<ListBox
|
||||
Classes="ContentListView"
|
||||
Items="{Binding AppState.SelectedTab.Parent.Items}"
|
||||
SelectedItem="{Binding AppState.SelectedTab.CurrentLocation}">
|
||||
IsEnabled="False"
|
||||
Items="{Binding AppState.SelectedTab.Parent.Items}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:ItemView ShowAttributes="False"/>
|
||||
@@ -202,6 +223,7 @@
|
||||
<Grid Grid.Column="4">
|
||||
<ListBox
|
||||
Classes="ContentListView"
|
||||
IsEnabled="False"
|
||||
x:Name="ChildItems"
|
||||
Items="{Binding AppState.SelectedTab.ChildContainer.Items}"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||
@@ -223,9 +245,78 @@
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding Inputs, Converter={StaticResource IsNotNullConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ItemsControl
|
||||
x:Name="InputList"
|
||||
Items="{Binding Inputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid MinWidth="400">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="200" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding InputElement.Text}" />
|
||||
<TextBox
|
||||
AttachedToVisualTree="InputText_AttachedToVisualTree"
|
||||
Grid.Column="1"
|
||||
GotFocus="InputText_GotFocus"
|
||||
LostFocus="InputText_LostFocus"
|
||||
KeyDown="InputText_KeyDown"
|
||||
Text="{Binding Value, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Command="{Binding ProcessInputsCommand}"
|
||||
Content="Ok" />
|
||||
<Button
|
||||
Command="{Binding CancelInputsCommand}"
|
||||
Content="Cancel" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding MessageBoxText, Converter={StaticResource IsNotNullConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="{Binding MessageBoxText}"/>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Command="{Binding ProcessMessageBoxCommand}"
|
||||
Content="Yes" />
|
||||
<Button
|
||||
Command="{Binding CancelMessageBoxCommand}"
|
||||
Content="No" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2">
|
||||
<Grid Grid.Row="3">
|
||||
<Grid IsVisible="{Binding AppState.ViewMode, Converter={StaticResource EqualityConverter}, ConverterParameter=RapidTravel}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1" />
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using FileTime.Avalonia.Misc;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using System.Linq;
|
||||
|
||||
namespace FileTime.Avalonia.Views
|
||||
{
|
||||
@@ -15,21 +18,13 @@ namespace FileTime.Avalonia.Views
|
||||
{
|
||||
if (value != DataContext)
|
||||
{
|
||||
if (DataContext is MainPageViewModel currentViewModel)
|
||||
{
|
||||
currentViewModel.FocusDefaultElement = null;
|
||||
}
|
||||
|
||||
DataContext = value;
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
//value.FocusDefaultElement = () => this.FindControl<ListBox>("CurrentItems")?.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InputElementWrapper? _inputElementWrapper;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -45,12 +40,58 @@ namespace FileTime.Avalonia.Views
|
||||
|
||||
public async void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
await ViewModel?.ProcessKeyDown(e.Key, e.KeyModifiers);
|
||||
if (_inputElementWrapper == null)
|
||||
{
|
||||
e.Handled = e.Handled || await ViewModel?.ProcessKeyDown(e.Key, e.KeyModifiers);
|
||||
}
|
||||
}
|
||||
|
||||
public async void OnKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
await ViewModel?.ProcessKeyUp(e.Key, e.KeyModifiers);
|
||||
if (_inputElementWrapper == null)
|
||||
{
|
||||
e.Handled = e.Handled || await ViewModel?.ProcessKeyUp(e.Key, e.KeyModifiers);
|
||||
}
|
||||
}
|
||||
|
||||
private void InputText_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && _inputElementWrapper == ViewModel!.Inputs.Last())
|
||||
{
|
||||
ViewModel.ProcessInputs();
|
||||
_inputElementWrapper = null;
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Escape && _inputElementWrapper == ViewModel!.Inputs.Last())
|
||||
{
|
||||
ViewModel.CancelInputs();
|
||||
_inputElementWrapper = null;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void InputText_GotFocus(object sender, GotFocusEventArgs e)
|
||||
{
|
||||
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper)
|
||||
{
|
||||
_inputElementWrapper = inputElementWrapper;
|
||||
}
|
||||
}
|
||||
|
||||
private void InputText_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper)
|
||||
{
|
||||
_inputElementWrapper = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void InputText_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs args)
|
||||
{
|
||||
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper && inputElementWrapper == ViewModel!.Inputs.First())
|
||||
{
|
||||
inputText.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.StateManagement;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Providers.Local.CommandHandlers
|
||||
{
|
||||
public class CopyCommandHandler : ICommandHandler
|
||||
{
|
||||
private readonly List<Thread> _copyOperations = new();
|
||||
private readonly ElementCreationStates _elementCreationStates;
|
||||
|
||||
public CopyCommandHandler(ElementCreationStates elementCreationStates)
|
||||
@@ -25,17 +25,14 @@ namespace FileTime.Providers.Local.CommandHandlers
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Execute(object command)
|
||||
public async Task ExecuteAsync(object command, TimeRunner timeRunner)
|
||||
{
|
||||
if (command is not CopyCommand copyCommand) throw new ArgumentException($"Can not execute command of type '{command.GetType()}'.");
|
||||
|
||||
var thread = new Thread(() => copyCommand.Execute(CopyElement));
|
||||
thread.Start();
|
||||
|
||||
_copyOperations.Add(thread);
|
||||
await copyCommand.Execute(CopyElement, timeRunner);
|
||||
}
|
||||
|
||||
public void CopyElement(IAbsolutePath sourcePath, IAbsolutePath targetPath)
|
||||
public static void CopyElement(AbsolutePath sourcePath, AbsolutePath targetPath)
|
||||
{
|
||||
using var sourceStream = File.OpenRead(sourcePath.Path);
|
||||
using var sourceReader = new BinaryReader(sourceStream);
|
||||
@@ -43,7 +40,7 @@ namespace FileTime.Providers.Local.CommandHandlers
|
||||
using var targetStream = File.OpenWrite(targetPath.Path);
|
||||
using var targetWriter = new BinaryWriter(targetStream);
|
||||
|
||||
var bufferSize = 1024 * 1024;
|
||||
const int bufferSize = 1024 * 1024;
|
||||
byte[] dataRead;
|
||||
|
||||
do
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
@@ -19,12 +20,15 @@ namespace FileTime.Providers.Local
|
||||
|
||||
public string? FullName { get; }
|
||||
public bool IsHidden => false;
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public IContentProvider Provider => this;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
public bool IsCaseInsensitive { get; }
|
||||
public bool CanDelete => false;
|
||||
public bool CanRename => false;
|
||||
|
||||
public LocalContentProvider(ILogger<LocalContentProvider> logger)
|
||||
{
|
||||
@@ -81,5 +85,7 @@ namespace FileTime.Providers.Local
|
||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult(_items);
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IContainer>?)_rootContainers);
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -24,13 +24,18 @@ namespace FileTime.Providers.Local
|
||||
public string Attributes => GetAttributes();
|
||||
|
||||
public DateTime CreatedAt => File.CreationTime;
|
||||
public bool CanDelete => true;
|
||||
public bool CanRename => true;
|
||||
|
||||
public LocalFile(FileInfo file, IContentProvider contentProvider)
|
||||
private readonly LocalFolder _parent;
|
||||
|
||||
public LocalFile(FileInfo file, LocalFolder parent, IContentProvider contentProvider)
|
||||
{
|
||||
_parent = parent;
|
||||
File = file;
|
||||
|
||||
Name = file.Name;
|
||||
FullName = file.FullName;
|
||||
FullName = parent.FullName + Constants.SeparatorChar + file.Name;
|
||||
Provider = contentProvider;
|
||||
}
|
||||
|
||||
@@ -41,6 +46,14 @@ namespace FileTime.Providers.Local
|
||||
File.Delete();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public async Task Rename(string newName)
|
||||
{
|
||||
if (_parent is LocalFolder parentFolder)
|
||||
{
|
||||
System.IO.File.Move(File.FullName, Path.Combine(parentFolder.Directory.FullName, newName));
|
||||
await _parent.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public string GetAttributes()
|
||||
{
|
||||
@@ -57,5 +70,7 @@ namespace FileTime.Providers.Local
|
||||
+ ((File.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-");
|
||||
}
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,12 @@ namespace FileTime.Providers.Local
|
||||
|
||||
public string FullName { get; }
|
||||
|
||||
public bool IsLoaded => _items != null;
|
||||
public bool CanDelete => true;
|
||||
public bool CanRename => true;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
public string Attributes => GetAttributes();
|
||||
|
||||
public DateTime CreatedAt => Directory.CreationTime;
|
||||
@@ -48,7 +53,7 @@ namespace FileTime.Providers.Local
|
||||
try
|
||||
{
|
||||
_containers = Directory.GetDirectories().Select(d => new LocalFolder(d, Provider, this)).OrderBy(d => d.Name).ToList().AsReadOnly();
|
||||
_elements = Directory.GetFiles().Select(f => new LocalFile(f, Provider)).OrderBy(f => f.Name).ToList().AsReadOnly();
|
||||
_elements = Directory.GetFiles().Select(f => new LocalFile(f, this, Provider)).OrderBy(f => f.Name).ToList().AsReadOnly();
|
||||
}
|
||||
catch { }
|
||||
|
||||
@@ -115,6 +120,14 @@ namespace FileTime.Providers.Local
|
||||
Directory.Delete(true);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public async Task Rename(string newName)
|
||||
{
|
||||
if (_parent is LocalFolder parentFolder)
|
||||
{
|
||||
System.IO.Directory.Move(Directory.FullName, Path.Combine(parentFolder.Directory.FullName, newName));
|
||||
await _parent.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
public string GetAttributes()
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
@@ -11,7 +12,7 @@ namespace FileTime.Providers.Smb
|
||||
private readonly IInputInterface _inputInterface;
|
||||
private readonly List<IContainer> _rootContainers;
|
||||
private readonly IReadOnlyList<IContainer> _rootContainersReadOnly;
|
||||
private readonly IReadOnlyList<IItem>? _items;
|
||||
private IReadOnlyList<IItem>? _items;
|
||||
private readonly IReadOnlyList<IElement>? _elements = new List<IElement>().AsReadOnly();
|
||||
|
||||
public string Name { get; } = "smb";
|
||||
@@ -19,14 +20,18 @@ namespace FileTime.Providers.Smb
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
public bool IsLoaded => true;
|
||||
|
||||
public IContentProvider Provider => this;
|
||||
public bool CanDelete => false;
|
||||
public bool CanRename => false;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
public SmbContentProvider(IInputInterface inputInterface)
|
||||
{
|
||||
_rootContainers = new List<IContainer>();
|
||||
_items = new List<IItem>();
|
||||
_rootContainersReadOnly = _rootContainers.AsReadOnly();
|
||||
_inputInterface = inputInterface;
|
||||
}
|
||||
@@ -40,6 +45,7 @@ namespace FileTime.Providers.Smb
|
||||
{
|
||||
container = new SmbServer(fullName, this, _inputInterface);
|
||||
_rootContainers.Add(container);
|
||||
_items = _rootContainers.OrderBy(c => c.Name).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
await Refresh();
|
||||
@@ -78,5 +84,7 @@ namespace FileTime.Providers.Smb
|
||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult(_items);
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IContainer>?)_rootContainersReadOnly);
|
||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -13,8 +13,11 @@ namespace FileTime.Providers.Smb
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
public bool CanDelete => true;
|
||||
public bool CanRename => true;
|
||||
|
||||
public IContentProvider Provider { get; }
|
||||
private IContainer _parent;
|
||||
|
||||
public SmbFile(string name, SmbContentProvider provider, IContainer parent)
|
||||
{
|
||||
@@ -22,16 +25,23 @@ namespace FileTime.Providers.Smb
|
||||
FullName = parent.FullName + Constants.SeparatorChar + Name;
|
||||
|
||||
Provider = provider;
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public Task Delete()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public Task Rename(string newName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetPrimaryAttributeText()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,12 @@ namespace FileTime.Providers.Smb
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
public bool IsLoaded => _items != null;
|
||||
|
||||
public SmbContentProvider Provider { get; }
|
||||
IContentProvider IItem.Provider => Provider;
|
||||
public bool CanDelete => true;
|
||||
public bool CanRename => true;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
@@ -77,6 +80,10 @@ namespace FileTime.Providers.Smb
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
public Task Rename(string newName)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public async Task Refresh()
|
||||
{
|
||||
|
||||
@@ -24,10 +24,13 @@ namespace FileTime.Providers.Smb
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
public bool IsLoaded => _items != null;
|
||||
|
||||
public SmbContentProvider Provider { get; }
|
||||
|
||||
IContentProvider IItem.Provider => Provider;
|
||||
public bool CanDelete => false;
|
||||
public bool CanRename => false;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
@@ -128,5 +131,7 @@ namespace FileTime.Providers.Smb
|
||||
}
|
||||
return _client;
|
||||
}
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Interactions;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
using SMBLibrary;
|
||||
@@ -20,9 +19,12 @@ namespace FileTime.Providers.Smb
|
||||
public string? FullName { get; }
|
||||
|
||||
public bool IsHidden => false;
|
||||
public bool IsLoaded => _items != null;
|
||||
|
||||
public SmbContentProvider Provider { get; }
|
||||
IContentProvider IItem.Provider => Provider;
|
||||
public bool CanDelete => false;
|
||||
public bool CanRename => false;
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
|
||||
@@ -150,5 +152,7 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
return (containers, elements);
|
||||
}
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user