TimeTravel
This commit is contained in:
@@ -1,35 +1,36 @@
|
|||||||
using FileTime.Core.Command;
|
using FileTime.Core.Command;
|
||||||
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Providers;
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
namespace FileTime.App.Core.Clipboard
|
namespace FileTime.App.Core.Clipboard
|
||||||
{
|
{
|
||||||
public class Clipboard : IClipboard
|
public class Clipboard : IClipboard
|
||||||
{
|
{
|
||||||
private readonly List<ClipboardItem> _content;
|
private readonly List<AbsolutePath> _content;
|
||||||
public IReadOnlyList<ClipboardItem> Content { get; }
|
public IReadOnlyList<AbsolutePath> Content { get; }
|
||||||
public Type? CommandType { get; private set; }
|
public Type? CommandType { get; private set; }
|
||||||
|
|
||||||
public Clipboard()
|
public Clipboard()
|
||||||
{
|
{
|
||||||
_content = new List<ClipboardItem>();
|
_content = new List<AbsolutePath>();
|
||||||
Content = _content.AsReadOnly();
|
Content = _content.AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddContent(IContentProvider contentProvider, string path)
|
public void AddContent(AbsolutePath absolutePath)
|
||||||
{
|
{
|
||||||
foreach (var content in _content)
|
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++)
|
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--);
|
_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.Command;
|
||||||
using FileTime.Core.Providers;
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
namespace FileTime.App.Core.Clipboard
|
namespace FileTime.App.Core.Clipboard
|
||||||
{
|
{
|
||||||
public interface IClipboard
|
public interface IClipboard
|
||||||
{
|
{
|
||||||
IReadOnlyList<ClipboardItem> Content { get; }
|
IReadOnlyList<AbsolutePath> Content { get; }
|
||||||
Type? CommandType { get; }
|
Type? CommandType { get; }
|
||||||
|
|
||||||
void AddContent(IContentProvider contentProvider, string path);
|
void AddContent(AbsolutePath absolutePath);
|
||||||
void Clear();
|
void Clear();
|
||||||
void RemoveContent(IContentProvider contentProvider, string path);
|
void RemoveContent(AbsolutePath absolutePath);
|
||||||
void SetCommand<T>() where T : ITransportationCommand;
|
void SetCommand<T>() where T : ITransportationCommand;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,26 +4,32 @@ namespace FileTime.App.Core.Command
|
|||||||
{
|
{
|
||||||
CloseTab,
|
CloseTab,
|
||||||
Copy,
|
Copy,
|
||||||
Cut,
|
|
||||||
GoUp,
|
|
||||||
MoveCursorDown,
|
|
||||||
MoveCursorUp,
|
|
||||||
Open,
|
|
||||||
Paste,
|
|
||||||
Select,
|
|
||||||
ToggleHidden,
|
|
||||||
CreateContainer,
|
CreateContainer,
|
||||||
CreateElement,
|
CreateElement,
|
||||||
MoveCursorUpPage,
|
Cut,
|
||||||
|
EnterRapidTravel,
|
||||||
|
GoToHome,
|
||||||
|
GoToProvider,
|
||||||
|
GoToRoot,
|
||||||
|
GoUp,
|
||||||
|
Delete,
|
||||||
|
MoveCursorDown,
|
||||||
MoveCursorDownPage,
|
MoveCursorDownPage,
|
||||||
MoveToTop,
|
MoveCursorUp,
|
||||||
|
MoveCursorUpPage,
|
||||||
MoveToBottom,
|
MoveToBottom,
|
||||||
MoveToFirst,
|
MoveToFirst,
|
||||||
MoveToLast,
|
MoveToLast,
|
||||||
GoToRoot,
|
MoveToTop,
|
||||||
GoToProvider,
|
Open,
|
||||||
GoToHome,
|
|
||||||
EnterRapidTravel,
|
|
||||||
OpenOrRun,
|
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 System.Collections.ObjectModel;
|
||||||
|
using AsyncEvent;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Providers;
|
|
||||||
|
|
||||||
namespace FileTime.App.Core.Tab
|
namespace FileTime.App.Core.Tab
|
||||||
{
|
{
|
||||||
public class TabState
|
public class TabState
|
||||||
{
|
{
|
||||||
private readonly Dictionary<IContainer, List<TabItem>> _selectedItems;
|
private readonly Dictionary<IContainer, List<AbsolutePath>> _markedItems;
|
||||||
private readonly Dictionary<IContainer, IReadOnlyList<TabItem>> _selectedItemsReadOnly;
|
private readonly Dictionary<IContainer, IReadOnlyList<AbsolutePath>> _markedItemsReadOnly;
|
||||||
public IReadOnlyDictionary<IContainer, IReadOnlyList<TabItem>> SelectedItems { get; }
|
public IReadOnlyDictionary<IContainer, IReadOnlyList<AbsolutePath>> MarkedItems { get; }
|
||||||
|
|
||||||
public FileTime.Core.Components.Tab Tab { 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)
|
public TabState(FileTime.Core.Components.Tab pane)
|
||||||
{
|
{
|
||||||
Tab = pane;
|
Tab = pane;
|
||||||
|
|
||||||
_selectedItems = new Dictionary<IContainer, List<TabItem>>();
|
_markedItems = new Dictionary<IContainer, List<AbsolutePath>>();
|
||||||
_selectedItemsReadOnly = new Dictionary<IContainer, IReadOnlyList<TabItem>>();
|
_markedItemsReadOnly = new Dictionary<IContainer, IReadOnlyList<AbsolutePath>>();
|
||||||
SelectedItems = new ReadOnlyDictionary<IContainer, IReadOnlyList<TabItem>>(_selectedItemsReadOnly);
|
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>();
|
var val = new List<AbsolutePath>();
|
||||||
_selectedItems.Add(container, val);
|
_markedItems.Add(container, val);
|
||||||
_selectedItemsReadOnly.Add(container, val.AsReadOnly());
|
_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];
|
var markedItems = _markedItems[container];
|
||||||
for (var i = 0; i < selectedItems.Count; i++)
|
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;
|
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)
|
public IReadOnlyList<AbsolutePath> GetCurrentMarkedItems(IContainer container)
|
||||||
? SelectedItems[currentLocation]
|
{
|
||||||
: new List<TabItem>().AsReadOnly();
|
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.Command;
|
||||||
using FileTime.Core.Providers;
|
using FileTime.Core.Providers;
|
||||||
using FileTime.Core.StateManagement;
|
using FileTime.Core.StateManagement;
|
||||||
|
using FileTime.Core.Timeline;
|
||||||
using FileTime.Providers.Local;
|
using FileTime.Providers.Local;
|
||||||
using FileTime.Providers.Smb;
|
using FileTime.Providers.Smb;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
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, LocalContentProvider>(sp => sp.GetService<LocalContentProvider>() ?? throw new Exception($"No {nameof(LocalContentProvider)} instance found"))
|
||||||
.AddSingleton<IContentProvider, SmbContentProvider>()
|
.AddSingleton<IContentProvider, SmbContentProvider>()
|
||||||
.AddSingleton<ElementCreationStates>()
|
.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.Command;
|
||||||
using FileTime.Core.Extensions;
|
using FileTime.Core.Extensions;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
@@ -28,8 +27,8 @@ namespace FileTime.ConsoleUI.App
|
|||||||
private async Task GoUp() => await _selectedTab!.GoUp();
|
private async Task GoUp() => await _selectedTab!.GoUp();
|
||||||
private async Task Open() => await _selectedTab!.Open();
|
private async Task Open() => await _selectedTab!.Open();
|
||||||
|
|
||||||
private async Task MoveCursorUpPage() => await _selectedTab!.SelectPreviousItem(_renderers[_selectedTab].PageSize);
|
private async Task MoveCursorUpPage() => await _selectedTab!.SelectPreviousItem(UI.Render.PageSize);
|
||||||
private async Task MoveCursorDownPage() => await _selectedTab!.SelectNextItem(_renderers[_selectedTab].PageSize);
|
private async Task MoveCursorDownPage() => await _selectedTab!.SelectNextItem(UI.Render.PageSize);
|
||||||
private async Task MoveCursorToTop() => await _selectedTab!.SelectFirstItem();
|
private async Task MoveCursorToTop() => await _selectedTab!.SelectFirstItem();
|
||||||
private async Task MoveCursorToBottom() => await _selectedTab!.SelectLastItem();
|
private async Task MoveCursorToBottom() => await _selectedTab!.SelectLastItem();
|
||||||
|
|
||||||
@@ -38,22 +37,7 @@ namespace FileTime.ConsoleUI.App
|
|||||||
const string hiddenFilterName = "filter_showhiddenelements";
|
const string hiddenFilterName = "filter_showhiddenelements";
|
||||||
|
|
||||||
var currentLocation = await _selectedTab!.GetCurrentLocation();
|
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);
|
var containerToOpen = await currentLocation.ToggleVirtualContainerInChain(hiddenFilterName, GenerateHiddenFilterVirtualContainer);
|
||||||
|
|
||||||
await _selectedTab.OpenContainer(containerToOpen);
|
await _selectedTab.OpenContainer(containerToOpen);
|
||||||
|
|
||||||
static async Task<VirtualContainer> GenerateHiddenFilterVirtualContainer(IContainer container)
|
static async Task<VirtualContainer> GenerateHiddenFilterVirtualContainer(IContainer container)
|
||||||
@@ -81,20 +65,9 @@ namespace FileTime.ConsoleUI.App
|
|||||||
|
|
||||||
public async Task Select()
|
public async Task Select()
|
||||||
{
|
{
|
||||||
var currentLocation = await _selectedTab!.GetCurrentLocation();
|
if (_selectedTab != null)
|
||||||
if (currentLocation != null)
|
|
||||||
{
|
{
|
||||||
var currentSelectedItem = await _selectedTab.GetCurrentSelectedItem()!;
|
await _tabStates[_selectedTab].MakrCurrentItem();
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,18 +76,18 @@ namespace FileTime.ConsoleUI.App
|
|||||||
_clipboard.Clear();
|
_clipboard.Clear();
|
||||||
_clipboard.SetCommand<CopyCommand>();
|
_clipboard.SetCommand<CopyCommand>();
|
||||||
|
|
||||||
var currentSelectedItems = await _paneStates[_selectedTab!].GetCurrentSelectedItems();
|
var currentSelectedItems = await _tabStates[_selectedTab!].GetCurrentMarkedItems();
|
||||||
if (currentSelectedItems.Count > 0)
|
if (currentSelectedItems.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var selectedItem in currentSelectedItems)
|
foreach (var selectedItem in currentSelectedItems)
|
||||||
{
|
{
|
||||||
_clipboard.AddContent(selectedItem.ContentProvider, selectedItem.Path);
|
_clipboard.AddContent(new AbsolutePath(selectedItem));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var currentSelectedItem = (await _selectedTab!.GetCurrentSelectedItem())!;
|
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
|
? virtualContainer.BaseContainer
|
||||||
: currentLocation;
|
: currentLocation;
|
||||||
|
|
||||||
_commandExecutor.ExecuteCommand(command);
|
await _timeRunner.AddCommand(command);
|
||||||
|
|
||||||
_clipboard.Clear();
|
_clipboard.Clear();
|
||||||
}
|
}
|
||||||
@@ -194,9 +167,9 @@ namespace FileTime.ConsoleUI.App
|
|||||||
|
|
||||||
private async Task HardDelete()
|
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();
|
var currentSelectedItem = await _selectedTab?.GetCurrentSelectedItem();
|
||||||
if (currentSelectedItems.Count > 0)
|
if (currentSelectedItems.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -212,7 +185,7 @@ namespace FileTime.ConsoleUI.App
|
|||||||
|
|
||||||
if (delete)
|
if (delete)
|
||||||
{
|
{
|
||||||
itemsToDelete = currentSelectedItems.Cast<IAbsolutePath>().ToList();
|
itemsToDelete = currentSelectedItems.Cast<AbsolutePath>().ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (currentSelectedItem != null)
|
else if (currentSelectedItem != null)
|
||||||
@@ -225,9 +198,9 @@ namespace FileTime.ConsoleUI.App
|
|||||||
|
|
||||||
if (delete)
|
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);
|
deleteCommand.ItemsToDelete.Add(itemToDelete);
|
||||||
}
|
}
|
||||||
|
|
||||||
_commandExecutor.ExecuteCommand(deleteCommand);
|
await _timeRunner.AddCommand(deleteCommand);
|
||||||
_clipboard.Clear();
|
_clipboard.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using FileTime.App.Core.Tab;
|
|||||||
using FileTime.ConsoleUI.App.UI.Color;
|
using FileTime.ConsoleUI.App.UI.Color;
|
||||||
using FileTime.Core.Command;
|
using FileTime.Core.Command;
|
||||||
using FileTime.App.Core.Command;
|
using FileTime.App.Core.Command;
|
||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.ConsoleUI.App
|
namespace FileTime.ConsoleUI.App
|
||||||
{
|
{
|
||||||
@@ -17,14 +18,14 @@ namespace FileTime.ConsoleUI.App
|
|||||||
{
|
{
|
||||||
private readonly List<Tab> _tabs = new();
|
private readonly List<Tab> _tabs = new();
|
||||||
private readonly Dictionary<Tab, Render> _renderers = 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 Tab? _selectedTab;
|
||||||
|
|
||||||
private readonly List<CommandBinding> _commandBindings = new();
|
private readonly List<CommandBinding> _commandBindings = new();
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IClipboard _clipboard;
|
private readonly IClipboard _clipboard;
|
||||||
private readonly IColoredConsoleRenderer _coloredConsoleRenderer;
|
private readonly IColoredConsoleRenderer _coloredConsoleRenderer;
|
||||||
private readonly CommandExecutor _commandExecutor;
|
private readonly TimeRunner _timeRunner;
|
||||||
private readonly ConsoleReader _consoleReader;
|
private readonly ConsoleReader _consoleReader;
|
||||||
private readonly IStyles _styles;
|
private readonly IStyles _styles;
|
||||||
private readonly List<ConsoleKeyInfo> _previousKeys = new();
|
private readonly List<ConsoleKeyInfo> _previousKeys = new();
|
||||||
@@ -35,14 +36,14 @@ namespace FileTime.ConsoleUI.App
|
|||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
IClipboard clipboard,
|
IClipboard clipboard,
|
||||||
IColoredConsoleRenderer coloredConsoleRenderer,
|
IColoredConsoleRenderer coloredConsoleRenderer,
|
||||||
CommandExecutor commandExecutor,
|
TimeRunner timeRunner,
|
||||||
ConsoleReader consoleReader,
|
ConsoleReader consoleReader,
|
||||||
IStyles styles)
|
IStyles styles)
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
_clipboard = clipboard;
|
_clipboard = clipboard;
|
||||||
_coloredConsoleRenderer = coloredConsoleRenderer;
|
_coloredConsoleRenderer = coloredConsoleRenderer;
|
||||||
_commandExecutor = commandExecutor;
|
_timeRunner = timeRunner;
|
||||||
_consoleReader = consoleReader;
|
_consoleReader = consoleReader;
|
||||||
_styles = styles;
|
_styles = styles;
|
||||||
InitCommandBindings();
|
InitCommandBindings();
|
||||||
@@ -60,7 +61,7 @@ namespace FileTime.ConsoleUI.App
|
|||||||
_tabs.Add(tab);
|
_tabs.Add(tab);
|
||||||
|
|
||||||
var paneState = new TabState(tab);
|
var paneState = new TabState(tab);
|
||||||
_paneStates.Add(tab, paneState);
|
_tabStates.Add(tab, paneState);
|
||||||
|
|
||||||
var renderer = _serviceProvider.GetService<Render>()!;
|
var renderer = _serviceProvider.GetService<Render>()!;
|
||||||
renderer.Init(tab, paneState);
|
renderer.Init(tab, paneState);
|
||||||
@@ -73,7 +74,7 @@ namespace FileTime.ConsoleUI.App
|
|||||||
{
|
{
|
||||||
_tabs.Remove(pane);
|
_tabs.Remove(pane);
|
||||||
_renderers.Remove(pane);
|
_renderers.Remove(pane);
|
||||||
_paneStates.Remove(pane);
|
_tabStates.Remove(pane);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitCommandBindings()
|
private void InitCommandBindings()
|
||||||
@@ -134,7 +135,7 @@ namespace FileTime.ConsoleUI.App
|
|||||||
Cut),
|
Cut),
|
||||||
new CommandBinding(
|
new CommandBinding(
|
||||||
"paste (merge)",
|
"paste (merge)",
|
||||||
Commands.Paste,
|
Commands.PasteMerge,
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||||
@@ -143,7 +144,7 @@ namespace FileTime.ConsoleUI.App
|
|||||||
PasteMerge),
|
PasteMerge),
|
||||||
new CommandBinding(
|
new CommandBinding(
|
||||||
"paste (overwrite)",
|
"paste (overwrite)",
|
||||||
Commands.Paste,
|
Commands.PasteOverwrite,
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||||
@@ -152,7 +153,7 @@ namespace FileTime.ConsoleUI.App
|
|||||||
PasteOverwrite),
|
PasteOverwrite),
|
||||||
new CommandBinding(
|
new CommandBinding(
|
||||||
"paste (skip)",
|
"paste (skip)",
|
||||||
Commands.Paste,
|
Commands.PasteSkip,
|
||||||
new[]
|
new[]
|
||||||
{
|
{
|
||||||
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
new ConsoleKeyInfo('p', ConsoleKey.P, false, false, false),
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace FileTime.ConsoleUI.App.UI
|
|||||||
public Tab Tab { get; private set; }
|
public Tab Tab { get; private set; }
|
||||||
public TabState TabState { 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)
|
public Render(IColoredConsoleRenderer coloredRenderer, IStyles appStyle)
|
||||||
{
|
{
|
||||||
_coloredRenderer = coloredRenderer;
|
_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)
|
if (isSelected)
|
||||||
{
|
{
|
||||||
backgroundColor = _appStyle.SelectedItemBackground;
|
backgroundColor = _appStyle.SelectedItemBackground;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace AsyncEvent
|
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 List<Func<TSender, TArg, Task>> _handlers;
|
||||||
private readonly Action<Func<TSender, TArg, Task>> _add;
|
private readonly Action<Func<TSender, TArg, Task>> _add;
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
return obj;
|
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) { }
|
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
|
namespace FileTime.Core.Command
|
||||||
{
|
{
|
||||||
public class CommandExecutor
|
public class CommandExecutor
|
||||||
@@ -9,15 +11,15 @@ namespace FileTime.Core.Command
|
|||||||
_commandHandlers = commandHandlers.ToList();
|
_commandHandlers = commandHandlers.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ExecuteCommand(ICommand command)
|
public async Task ExecuteCommandAsync(ICommand command, TimeRunner timeRunner)
|
||||||
{
|
{
|
||||||
if (command is IExecutableCommand executableCommand)
|
if (command is IExecutableCommand executableCommand)
|
||||||
{
|
{
|
||||||
executableCommand.Execute();
|
await executableCommand.Execute(timeRunner);
|
||||||
}
|
}
|
||||||
else
|
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 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 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)
|
foreach (var source in sources)
|
||||||
{
|
{
|
||||||
var item = await source.ContentProvider.GetByPath(source.Path);
|
var item = await source.Resolve();
|
||||||
|
|
||||||
if (item is IContainer container)
|
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 childDirectories = (await container.GetContainers())!.Select(d => new AbsolutePath(d));
|
||||||
var childFiles = (await container.GetElements())!.Select(f => new AbsolutePath(item.Provider, f.FullName!));
|
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)
|
else if (item is IElement element)
|
||||||
{
|
{
|
||||||
var targetName = element.Name;
|
var targetName = element.Name;
|
||||||
|
|
||||||
var targetNameExists = await target.IsExists(targetName);
|
var targetNameExists = await target.IsExists(targetName);
|
||||||
if (transportMode == TransportMode.Merge)
|
if (transportMode == Command.TransportMode.Merge)
|
||||||
{
|
{
|
||||||
for (var i = 0; targetNameExists; i++)
|
for (var i = 0; targetNameExists; i++)
|
||||||
{
|
{
|
||||||
targetName = element.Name + (i == 0 ? "_" : $"_{i}");
|
targetName = element.Name + (i == 0 ? "_" : $"_{i}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (transportMode == TransportMode.Skip && targetNameExists)
|
else if (transportMode == Command.TransportMode.Skip && targetNameExists)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetPath = target.FullName + Constants.SeparatorChar + targetName;
|
_copyOperation?.Invoke(new AbsolutePath(element), AbsolutePath.FromParentAndChildName(target, targetName));
|
||||||
|
|
||||||
copy(new AbsolutePath(source.ContentProvider, element.FullName!), new AbsolutePath(target.Provider, targetPath));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.Core.Command
|
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;
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.Core.Command
|
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.Models;
|
||||||
using FileTime.Core.Timeline;
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
@@ -5,37 +6,64 @@ namespace FileTime.Core.Command
|
|||||||
{
|
{
|
||||||
public class DeleteCommand : IExecutableCommand
|
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)
|
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)
|
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 child.Delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
await item.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();
|
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
|
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
|
namespace FileTime.Core.Command
|
||||||
{
|
{
|
||||||
public interface ICommandHandler
|
public interface ICommandHandler
|
||||||
{
|
{
|
||||||
bool CanHandle(object command);
|
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
|
namespace FileTime.Core.Command
|
||||||
{
|
{
|
||||||
public interface IExecutableCommand : ICommand
|
public interface IExecutableCommand : ICommand
|
||||||
{
|
{
|
||||||
Task Execute();
|
Task Execute(TimeRunner timeRunner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,8 @@ namespace FileTime.Core.Command
|
|||||||
{
|
{
|
||||||
public interface ITransportationCommand : ICommand
|
public interface ITransportationCommand : ICommand
|
||||||
{
|
{
|
||||||
IList<IAbsolutePath> Sources { get; }
|
IList<AbsolutePath>? Sources { get; }
|
||||||
IContainer Target { get; set;}
|
IContainer? Target { get; set;}
|
||||||
TransportMode TransportMode { get; set; }
|
TransportMode? TransportMode { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,12 +5,17 @@ namespace FileTime.Core.Command
|
|||||||
{
|
{
|
||||||
public class MoveCommand : ITransportationCommand
|
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 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();
|
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 IItem? _currentSelectedItem;
|
||||||
private IContainer _currentLocation;
|
private IContainer _currentLocation;
|
||||||
|
private string? _lastPath;
|
||||||
|
|
||||||
public int CurrentSelectedIndex { get; private set; }
|
public int CurrentSelectedIndex { get; private set; }
|
||||||
|
|
||||||
public AsyncEventHandler CurrentLocationChanged = new();
|
public AsyncEventHandler CurrentLocationChanged = new();
|
||||||
@@ -33,10 +34,10 @@ namespace FileTime.Core.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
_currentLocation = value;
|
_currentLocation = value;
|
||||||
await CurrentLocationChanged?.InvokeAsync(this, AsyncEventArgs.Empty);
|
await CurrentLocationChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||||
|
|
||||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
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);
|
_currentLocation.Refreshed.Add(HandleCurrentLocationRefresh);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,14 +59,71 @@ namespace FileTime.Core.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
_currentSelectedItem = itemToSelect;
|
_currentSelectedItem = itemToSelect;
|
||||||
|
_lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName);
|
||||||
CurrentSelectedIndex = await GetItemIndex(itemToSelect);
|
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)
|
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())!;
|
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||||
if (currentSelectedName != null)
|
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 string Text { get; }
|
||||||
public InputType InputType { 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;
|
Text = text;
|
||||||
InputType = inputType;
|
InputType = inputType;
|
||||||
|
DefaultValue = defaultValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ namespace FileTime.Core.Interactions
|
|||||||
public enum InputType
|
public enum InputType
|
||||||
{
|
{
|
||||||
Text,
|
Text,
|
||||||
Password
|
Password,
|
||||||
|
Bool
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,97 @@
|
|||||||
using FileTime.Core.Providers;
|
using FileTime.Core.Providers;
|
||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.Core.Models
|
namespace FileTime.Core.Models
|
||||||
{
|
{
|
||||||
public class AbsolutePath : IAbsolutePath
|
public sealed class AbsolutePath
|
||||||
{
|
{
|
||||||
public IContentProvider ContentProvider { get; }
|
public IContentProvider ContentProvider { get; }
|
||||||
|
public IContentProvider? VirtualContentProvider { get; }
|
||||||
|
|
||||||
public string Path { 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;
|
ContentProvider = contentProvider;
|
||||||
Path = path;
|
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<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default);
|
||||||
|
|
||||||
Task Refresh();
|
Task Refresh();
|
||||||
IContainer? GetParent();
|
|
||||||
Task<IItem?> GetByPath(string path);
|
Task<IItem?> GetByPath(string path);
|
||||||
Task<IContainer> CreateContainer(string name);
|
Task<IContainer> CreateContainer(string name);
|
||||||
Task<IElement> CreateElement(string name);
|
Task<IElement> CreateElement(string name);
|
||||||
@@ -18,6 +17,8 @@ namespace FileTime.Core.Models
|
|||||||
|
|
||||||
Task<IContainer> Clone();
|
Task<IContainer> Clone();
|
||||||
|
|
||||||
|
bool IsLoaded { get; }
|
||||||
|
|
||||||
AsyncEventHandler Refreshed { get; }
|
AsyncEventHandler Refreshed { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,11 @@ namespace FileTime.Core.Models
|
|||||||
string Name { get; }
|
string Name { get; }
|
||||||
string? FullName { get; }
|
string? FullName { get; }
|
||||||
bool IsHidden { get; }
|
bool IsHidden { get; }
|
||||||
|
bool CanDelete { get; }
|
||||||
|
bool CanRename { get; }
|
||||||
IContentProvider Provider { get; }
|
IContentProvider Provider { get; }
|
||||||
Task Delete();
|
Task Delete();
|
||||||
|
Task Rename(string newName);
|
||||||
|
IContainer? GetParent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,6 +24,9 @@ namespace FileTime.Core.Models
|
|||||||
public string? FullName => BaseContainer.FullName;
|
public string? FullName => BaseContainer.FullName;
|
||||||
|
|
||||||
public bool IsHidden => BaseContainer.IsHidden;
|
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;
|
public IContentProvider Provider => BaseContainer.Provider;
|
||||||
|
|
||||||
@@ -159,5 +162,7 @@ namespace FileTime.Core.Models
|
|||||||
VirtualContainerName
|
VirtualContainerName
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task Rename(string newName) => await BaseContainer.Rename(newName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
using System;
|
||||||
using AsyncEvent;
|
using AsyncEvent;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
|
|
||||||
@@ -16,8 +17,11 @@ namespace FileTime.Core.Providers
|
|||||||
public string? FullName => null;
|
public string? FullName => null;
|
||||||
|
|
||||||
public bool IsHidden => false;
|
public bool IsHidden => false;
|
||||||
|
public bool IsLoaded => true;
|
||||||
|
|
||||||
public IContentProvider Provider => null;
|
public IContentProvider Provider => null;
|
||||||
|
public bool CanDelete => false;
|
||||||
|
public bool CanRename => false;
|
||||||
|
|
||||||
public AsyncEventHandler Refreshed { get; } = new();
|
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<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
|
||||||
|
|
||||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
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;
|
using FileTime.Core.Providers;
|
||||||
|
|
||||||
namespace FileTime.Core.Timeline
|
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="ItemBackgroundColor">#00000000</Color>
|
||||||
<Color x:Key="AlternativeItemBackgroundColor">#10000000</Color>
|
<Color x:Key="AlternativeItemBackgroundColor">#10000000</Color>
|
||||||
<Color x:Key="SelectedItemBackgroundColor">#93a1a1</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="ForegroundColor">#93a1a1</Color>
|
||||||
<Color x:Key="AccentForegroundColor">#268bd2</Color>
|
<Color x:Key="AccentForegroundColor">#268bd2</Color>
|
||||||
<Color x:Key="LightForegroundColor">#7793a1a1</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="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>
|
<Color x:Key="ErrorColor">#dc322f</Color>
|
||||||
|
|
||||||
@@ -48,6 +54,15 @@
|
|||||||
<SolidColorBrush
|
<SolidColorBrush
|
||||||
x:Key="SelectedItemBackgroundBrush"
|
x:Key="SelectedItemBackgroundBrush"
|
||||||
Color="{DynamicResource SelectedItemBackgroundColor}" />
|
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
|
<SolidColorBrush
|
||||||
x:Key="ForegroundBrush"
|
x:Key="ForegroundBrush"
|
||||||
@@ -59,11 +74,20 @@
|
|||||||
x:Key="LightForegroundBrush"
|
x:Key="LightForegroundBrush"
|
||||||
Color="{DynamicResource LightForegroundColor}" />
|
Color="{DynamicResource LightForegroundColor}" />
|
||||||
<SolidColorBrush
|
<SolidColorBrush
|
||||||
x:Key="AlternativeForegroundBrush"
|
x:Key="AlternativeItemForegroundBrush"
|
||||||
Color="{DynamicResource AlternativeForegroundColor}" />
|
Color="{DynamicResource AlternativeItemForegroundColor}" />
|
||||||
<SolidColorBrush
|
<SolidColorBrush
|
||||||
x:Key="SelectedItemForegroundBrush"
|
x:Key="SelectedItemForegroundBrush"
|
||||||
Color="{DynamicResource SelectedItemForegroundColor}" />
|
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
|
<SolidColorBrush
|
||||||
x:Key="ErrorBrush"
|
x:Key="ErrorBrush"
|
||||||
@@ -79,14 +103,23 @@
|
|||||||
<converters:ItemViewModeToBrushConverter
|
<converters:ItemViewModeToBrushConverter
|
||||||
x:Key="ItemViewModeToForegroundConverter"
|
x:Key="ItemViewModeToForegroundConverter"
|
||||||
DefaultBrush="{StaticResource ForegroundBrush}"
|
DefaultBrush="{StaticResource ForegroundBrush}"
|
||||||
AlternativeBrush="{StaticResource AlternativeForegroundBrush}"
|
AlternativeBrush="{StaticResource AlternativeItemForegroundBrush}"
|
||||||
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"/>
|
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"
|
||||||
|
MarkedBrush="{StaticResource MarkedItemForegroundBrush}"
|
||||||
|
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemForegroundBrush}"
|
||||||
|
MarkedSelectedBrush="{StaticResource MarkedSelectedItemForegroundBrush}"/>
|
||||||
<converters:ItemViewModeToBrushConverter
|
<converters:ItemViewModeToBrushConverter
|
||||||
x:Key="ItemViewModeToBackgroundConverter"
|
x:Key="ItemViewModeToBackgroundConverter"
|
||||||
DefaultBrush="{StaticResource ItemBackgroundBrush}"
|
DefaultBrush="{StaticResource ItemBackgroundBrush}"
|
||||||
AlternativeBrush="{StaticResource AlternativeItemBackgroundBrush}"
|
AlternativeBrush="{StaticResource AlternativeItemBackgroundBrush}"
|
||||||
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"/>
|
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"
|
||||||
|
MarkedBrush="{StaticResource MarkedItemBackgroundBrush}"
|
||||||
|
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemBackgroundBrush}"
|
||||||
|
MarkedSelectedBrush="{StaticResource MarkedSelectedItemBackgroundBrush}"/>
|
||||||
<converters:ContextMenuGenerator x:Key="ContextMenuGenerator"/>
|
<converters:ContextMenuGenerator x:Key="ContextMenuGenerator"/>
|
||||||
|
<converters:ItemToImageConverter x:Key="ItemToImageConverter"/>
|
||||||
|
<converters:IsNullConverter x:Key="IsNullConverter"/>
|
||||||
|
<converters:IsNullConverter x:Key="IsNotNullConverter" Inverse="true"/>
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ namespace FileTime.Avalonia
|
|||||||
.RegisterDefaultServices()
|
.RegisterDefaultServices()
|
||||||
.AddViewModels()
|
.AddViewModels()
|
||||||
.AddServices()
|
.AddServices()
|
||||||
|
.RegisterCommandHandlers()
|
||||||
.BuildServiceProvider();
|
.BuildServiceProvider();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
using FileTime.Core.Components;
|
using MvvmGen;
|
||||||
using MvvmGen;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
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
|
namespace FileTime.Avalonia.Application
|
||||||
{
|
{
|
||||||
@@ -11,7 +13,7 @@ namespace FileTime.Avalonia.Application
|
|||||||
public partial class AppState
|
public partial class AppState
|
||||||
{
|
{
|
||||||
[Property]
|
[Property]
|
||||||
private ObservableCollection<TabContainer> _tabs = new ObservableCollection<TabContainer>();
|
private ObservableCollection<TabContainer> _tabs = new();
|
||||||
|
|
||||||
[Property]
|
[Property]
|
||||||
private TabContainer _selectedTab;
|
private TabContainer _selectedTab;
|
||||||
@@ -24,7 +26,66 @@ namespace FileTime.Avalonia.Application
|
|||||||
|
|
||||||
partial void OnInitialize()
|
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 FileTime.Avalonia.ViewModels;
|
||||||
using MvvmGen;
|
using MvvmGen;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using FileTime.App.Core.Tab;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace FileTime.Avalonia.Application
|
namespace FileTime.Avalonia.Application
|
||||||
{
|
{
|
||||||
@@ -18,8 +18,11 @@ namespace FileTime.Avalonia.Application
|
|||||||
[Inject(typeof(ItemNameConverterService))]
|
[Inject(typeof(ItemNameConverterService))]
|
||||||
[Inject(typeof(LocalContentProvider))]
|
[Inject(typeof(LocalContentProvider))]
|
||||||
[Inject(typeof(Tab))]
|
[Inject(typeof(Tab))]
|
||||||
public partial class TabContainer
|
public partial class TabContainer : INewItemProcessor
|
||||||
{
|
{
|
||||||
|
[Property]
|
||||||
|
private TabState _tabState;
|
||||||
|
|
||||||
[Property]
|
[Property]
|
||||||
private ContainerViewModel _parent;
|
private ContainerViewModel _parent;
|
||||||
|
|
||||||
@@ -45,25 +48,33 @@ namespace FileTime.Avalonia.Application
|
|||||||
if (_selectedItem != value)// && value != null
|
if (_selectedItem != value)// && value != null
|
||||||
{
|
{
|
||||||
_selectedItem = value;
|
_selectedItem = value;
|
||||||
|
|
||||||
OnPropertyChanged("SelectedItem");
|
OnPropertyChanged("SelectedItem");
|
||||||
SelectedItemChanged();
|
SelectedItemChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
partial void OnInitialize()
|
||||||
|
{
|
||||||
|
_tabState = new TabState(Tab);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task Init(int tabNumber)
|
public async Task Init(int tabNumber)
|
||||||
{
|
{
|
||||||
TabNumber = tabNumber;
|
TabNumber = tabNumber;
|
||||||
Tab.CurrentLocationChanged.Add(Tab_CurrentLocationChanged);
|
Tab.CurrentLocationChanged.Add(Tab_CurrentLocationChanged);
|
||||||
Tab.CurrentSelectedItemChanged.Add(Tab_CurrentSelectedItemChanged);
|
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();
|
await CurrentLocation.Init();
|
||||||
|
|
||||||
var parent = (await Tab.GetCurrentLocation()).GetParent();
|
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
Parent = new ContainerViewModel(parent, ItemNameConverterService);
|
parent.ChildrenToAdopt.Add(CurrentLocation);
|
||||||
|
Parent = parent;
|
||||||
await Parent.Init();
|
await Parent.Init();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -74,16 +85,28 @@ namespace FileTime.Avalonia.Application
|
|||||||
await UpdateCurrentSelectedItem();
|
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)
|
private async Task Tab_CurrentLocationChanged(object? sender, AsyncEventArgs e)
|
||||||
{
|
{
|
||||||
var currentLocation = await Tab.GetCurrentLocation();
|
var currentLocation = await Tab.GetCurrentLocation();
|
||||||
CurrentLocation = new ContainerViewModel(currentLocation, ItemNameConverterService);
|
var parent = GenerateParent(currentLocation);
|
||||||
|
CurrentLocation = new ContainerViewModel(this, parent, currentLocation, ItemNameConverterService);
|
||||||
await CurrentLocation.Init();
|
await CurrentLocation.Init();
|
||||||
|
|
||||||
var parent = currentLocation.GetParent();
|
|
||||||
if (parent != null)
|
if (parent != null)
|
||||||
{
|
{
|
||||||
Parent = new ContainerViewModel(parent, ItemNameConverterService);
|
parent.ChildrenToAdopt.Add(CurrentLocation);
|
||||||
|
Parent = parent;
|
||||||
await Parent.Init();
|
await Parent.Init();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -97,39 +120,89 @@ namespace FileTime.Avalonia.Application
|
|||||||
await UpdateCurrentSelectedItem();
|
await UpdateCurrentSelectedItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateCurrentSelectedItem()
|
public async Task UpdateCurrentSelectedItem()
|
||||||
{
|
{
|
||||||
var tabCurrentSelectenItem = await Tab.GetCurrentSelectedItem();
|
try
|
||||||
IItemViewModel? currentSelectenItem = null;
|
|
||||||
if (tabCurrentSelectenItem == null)
|
|
||||||
{
|
{
|
||||||
SelectedItem = null;
|
var tabCurrentSelectenItem = await Tab.GetCurrentSelectedItem();
|
||||||
ChildContainer = null;
|
IItemViewModel? currentSelectenItem = null;
|
||||||
}
|
if (tabCurrentSelectenItem == 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;
|
SelectedItem = null;
|
||||||
ChildContainer = 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();
|
var items = await _currentLocation.GetItems();
|
||||||
foreach (var item in items)
|
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);
|
(await Tab.GetCurrentLocation())?.CreateContainer(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task CreateElement(string name)
|
||||||
|
{
|
||||||
|
(await Tab.GetCurrentLocation())?.CreateElement(name);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task OpenContainer(IContainer container)
|
public async Task OpenContainer(IContainer container)
|
||||||
{
|
{
|
||||||
await Tab.OpenContainer(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? DefaultBrush { get; set; }
|
||||||
public Brush? AlternativeBrush { get; set; }
|
public Brush? AlternativeBrush { get; set; }
|
||||||
public Brush? SelectedBrush { get; set; }
|
public Brush? SelectedBrush { get; set; }
|
||||||
|
public Brush? MarkedBrush { get; set; }
|
||||||
|
public Brush? MarkedSelectedBrush { get; set; }
|
||||||
|
public Brush? MarkedAlternativeBrush { get; set; }
|
||||||
|
|
||||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
@@ -21,6 +24,9 @@ namespace FileTime.Avalonia.Converters
|
|||||||
ItemViewMode.Default => DefaultBrush,
|
ItemViewMode.Default => DefaultBrush,
|
||||||
ItemViewMode.Alternative => AlternativeBrush,
|
ItemViewMode.Alternative => AlternativeBrush,
|
||||||
ItemViewMode.Selected => SelectedBrush,
|
ItemViewMode.Selected => SelectedBrush,
|
||||||
|
ItemViewMode.Marked => MarkedBrush,
|
||||||
|
ItemViewMode.MarkedSelected => MarkedSelectedBrush,
|
||||||
|
ItemViewMode.MarkedAlternative => MarkedAlternativeBrush,
|
||||||
_ => throw new NotImplementedException()
|
_ => 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 string Value { get; set; }
|
||||||
|
|
||||||
public InputElementWrapper(InputElement inputElement)
|
public InputElementWrapper(InputElement inputElement, string? defaultValue = null)
|
||||||
{
|
{
|
||||||
InputElement = inputElement;
|
InputElement = inputElement;
|
||||||
|
Value = defaultValue ?? "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using FileTime.Avalonia.Application;
|
|||||||
using FileTime.Avalonia.Models;
|
using FileTime.Avalonia.Models;
|
||||||
using FileTime.Avalonia.ViewModels;
|
using FileTime.Avalonia.ViewModels;
|
||||||
using MvvmGen;
|
using MvvmGen;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace FileTime.Avalonia.Services
|
namespace FileTime.Avalonia.Services
|
||||||
@@ -20,9 +21,8 @@ namespace FileTime.Avalonia.Services
|
|||||||
{
|
{
|
||||||
var nameLeft = itemViewModel.Item.Name;
|
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 before = rapidTextStart > 0 ? nameLeft.Substring(0, rapidTextStart) : null;
|
||||||
var rapidTravel = nameLeft.Substring(rapidTextStart, rapidTravelText.Length);
|
var rapidTravel = nameLeft.Substring(rapidTextStart, rapidTravelText.Length);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using FileTime.Avalonia.Application;
|
using FileTime.Avalonia.Application;
|
||||||
using FileTime.Avalonia.Services;
|
using FileTime.Avalonia.Services;
|
||||||
using FileTime.Avalonia.ViewModels;
|
using FileTime.Avalonia.ViewModels;
|
||||||
|
using FileTime.Core.Command;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
namespace FileTime.Avalonia
|
namespace FileTime.Avalonia
|
||||||
@@ -29,6 +30,15 @@ namespace FileTime.Avalonia
|
|||||||
throw new System.Exception("TODO: implement linux contextmenu provider");
|
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;
|
return serviceCollection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using FileTime.Avalonia.Application;
|
||||||
|
|
||||||
namespace FileTime.Avalonia.ViewModels
|
namespace FileTime.Avalonia.ViewModels
|
||||||
{
|
{
|
||||||
@@ -16,8 +17,9 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
[Inject(typeof(ItemNameConverterService))]
|
[Inject(typeof(ItemNameConverterService))]
|
||||||
public partial class ContainerViewModel : IItemViewModel
|
public partial class ContainerViewModel : IItemViewModel
|
||||||
{
|
{
|
||||||
private bool isRefreshing;
|
private bool _isRefreshing;
|
||||||
private bool isInitialized;
|
private bool _isInitialized;
|
||||||
|
private INewItemProcessor _newItemProcessor;
|
||||||
|
|
||||||
[Property]
|
[Property]
|
||||||
private IContainer _container;
|
private IContainer _container;
|
||||||
@@ -25,29 +27,39 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
[Property]
|
[Property]
|
||||||
private bool _isSelected;
|
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]
|
[Property]
|
||||||
private bool _isAlternative;
|
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(IsSelected))]
|
||||||
[PropertyInvalidate(nameof(IsAlternative))]
|
[PropertyInvalidate(nameof(IsAlternative))]
|
||||||
|
[PropertyInvalidate(nameof(IsMarked))]
|
||||||
public ItemViewMode ViewMode =>
|
public ItemViewMode ViewMode =>
|
||||||
IsSelected
|
(IsMarked, IsSelected, IsAlternative) switch
|
||||||
? ItemViewMode.Selected
|
{
|
||||||
: IsAlternative
|
(true, true, _) => ItemViewMode.MarkedSelected,
|
||||||
? ItemViewMode.Alternative
|
(true, false, true) => ItemViewMode.MarkedAlternative,
|
||||||
: ItemViewMode.Default;
|
(false, true, _) => ItemViewMode.Selected,
|
||||||
|
(false, false, true) => ItemViewMode.Alternative,
|
||||||
|
(true, false, false) => ItemViewMode.Marked,
|
||||||
|
_ => ItemViewMode.Default
|
||||||
|
};
|
||||||
|
|
||||||
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
|
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
|
||||||
|
|
||||||
@@ -56,7 +68,7 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!isInitialized) Task.Run(Refresh);
|
if (!_isInitialized) Task.Run(Refresh);
|
||||||
return _containers;
|
return _containers;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +78,7 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!isInitialized) Task.Run(Refresh);
|
if (!_isInitialized) Task.Run(Refresh);
|
||||||
return _elements;
|
return _elements;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,13 +88,16 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (!isInitialized) Task.Run(Refresh);
|
if (!_isInitialized) Task.Run(Refresh);
|
||||||
return _items;
|
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 = container;
|
||||||
Container.Refreshed.Add(Container_Refreshed);
|
Container.Refreshed.Add(Container_Refreshed);
|
||||||
}
|
}
|
||||||
@@ -94,7 +109,7 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
await Refresh(initializeChildren);
|
await Refresh(initializeChildren);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task Container_Refreshed(object sender, AsyncEventArgs e)
|
private async Task Container_Refreshed(object? sender, AsyncEventArgs e)
|
||||||
{
|
{
|
||||||
await Refresh(false);
|
await Refresh(false);
|
||||||
}
|
}
|
||||||
@@ -105,16 +120,16 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
}
|
}
|
||||||
private async Task Refresh(bool initializeChildren)
|
private async Task Refresh(bool initializeChildren)
|
||||||
{
|
{
|
||||||
if (isRefreshing) return;
|
if (_isRefreshing) return;
|
||||||
|
|
||||||
isInitialized = true;
|
_isInitialized = true;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
isRefreshing = true;
|
_isRefreshing = true;
|
||||||
|
|
||||||
var containers = (await _container.GetContainers()).Select(c => new ContainerViewModel(c, 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 => new ElementViewModel(e, ItemNameConverterService)).ToList();
|
var elements = (await _container.GetElements()).Select(e => AdoptOrCreateItem(e, (e2) => new ElementViewModel(e2, this, ItemNameConverterService))).ToList();
|
||||||
|
|
||||||
_containers.Clear();
|
_containers.Clear();
|
||||||
_elements.Clear();
|
_elements.Clear();
|
||||||
@@ -141,24 +156,51 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
}
|
}
|
||||||
catch { }
|
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()
|
public async Task<ObservableCollection<ContainerViewModel>> GetContainers()
|
||||||
{
|
{
|
||||||
if (!isInitialized) await Task.Run(Refresh);
|
if (!_isInitialized) await Task.Run(Refresh);
|
||||||
return _containers;
|
return _containers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ObservableCollection<ElementViewModel>> GetElements()
|
public async Task<ObservableCollection<ElementViewModel>> GetElements()
|
||||||
{
|
{
|
||||||
if (!isInitialized) await Task.Run(Refresh);
|
if (!_isInitialized) await Task.Run(Refresh);
|
||||||
return _elements;
|
return _elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ObservableCollection<IItemViewModel>> GetItems()
|
public async Task<ObservableCollection<IItemViewModel>> GetItems()
|
||||||
{
|
{
|
||||||
if (!isInitialized) await Task.Run(Refresh);
|
if (!_isInitialized) await Task.Run(Refresh);
|
||||||
return _items;
|
return _items;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,20 +21,32 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
[Property]
|
[Property]
|
||||||
private bool _isAlternative;
|
private bool _isAlternative;
|
||||||
|
|
||||||
|
[Property]
|
||||||
|
private bool _isMarked;
|
||||||
|
|
||||||
|
[Property]
|
||||||
|
private ContainerViewModel? _parent;
|
||||||
|
|
||||||
[PropertyInvalidate(nameof(IsSelected))]
|
[PropertyInvalidate(nameof(IsSelected))]
|
||||||
[PropertyInvalidate(nameof(IsAlternative))]
|
[PropertyInvalidate(nameof(IsAlternative))]
|
||||||
|
[PropertyInvalidate(nameof(IsMarked))]
|
||||||
public ItemViewMode ViewMode =>
|
public ItemViewMode ViewMode =>
|
||||||
IsSelected
|
(IsMarked, IsSelected, IsAlternative) switch
|
||||||
? ItemViewMode.Selected
|
{
|
||||||
: IsAlternative
|
(true, true, _) => ItemViewMode.MarkedSelected,
|
||||||
? ItemViewMode.Alternative
|
(true, false, true) => ItemViewMode.MarkedAlternative,
|
||||||
: ItemViewMode.Default;
|
(false, true, _) => ItemViewMode.Selected,
|
||||||
|
(false, false, true) => ItemViewMode.Alternative,
|
||||||
|
(true, false, false) => ItemViewMode.Marked,
|
||||||
|
_ => ItemViewMode.Default
|
||||||
|
};
|
||||||
|
|
||||||
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
|
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;
|
Element = element;
|
||||||
|
Parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvalidateDisplayName() => OnPropertyChanged(nameof(DisplayName));
|
public void InvalidateDisplayName() => OnPropertyChanged(nameof(DisplayName));
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Avalonia.Models;
|
using FileTime.Avalonia.Models;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace FileTime.Avalonia.ViewModels
|
namespace FileTime.Avalonia.ViewModels
|
||||||
{
|
{
|
||||||
@@ -12,6 +10,8 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
bool IsSelected { get; set; }
|
bool IsSelected { get; set; }
|
||||||
|
|
||||||
bool IsAlternative { get; set; }
|
bool IsAlternative { get; set; }
|
||||||
|
bool IsMarked { get; set; }
|
||||||
|
ContainerViewModel? Parent{ get; set; }
|
||||||
|
|
||||||
ItemViewMode ViewMode { get; }
|
ItemViewMode ViewMode { get; }
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
{
|
{
|
||||||
public enum ItemViewMode
|
public enum ItemViewMode
|
||||||
{
|
{
|
||||||
Selected,
|
Default,
|
||||||
Alternative,
|
Alternative,
|
||||||
Default
|
Selected,
|
||||||
|
Marked,
|
||||||
|
MarkedSelected,
|
||||||
|
MarkedAlternative
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ using System.Linq;
|
|||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
using FileTime.App.Core.Clipboard;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using FileTime.Core.Command;
|
||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.Avalonia.ViewModels
|
namespace FileTime.Avalonia.ViewModels
|
||||||
{
|
{
|
||||||
@@ -34,6 +38,9 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
private List<CommandBinding> _commandBindings = new();
|
private List<CommandBinding> _commandBindings = new();
|
||||||
private List<CommandBinding> _universalCommandBindings = new();
|
private List<CommandBinding> _universalCommandBindings = new();
|
||||||
|
|
||||||
|
private IClipboard _clipboard;
|
||||||
|
private TimeRunner _timeRunner;
|
||||||
|
|
||||||
private Action? _inputHandler;
|
private Action? _inputHandler;
|
||||||
|
|
||||||
[Property]
|
[Property]
|
||||||
@@ -51,10 +58,16 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
[Property]
|
[Property]
|
||||||
private List<RootDriveInfo> _rootDriveInfos;
|
private List<RootDriveInfo> _rootDriveInfos;
|
||||||
|
|
||||||
public Action? FocusDefaultElement { get; set; }
|
[Property]
|
||||||
|
private string _messageBoxText;
|
||||||
|
|
||||||
|
public IReadOnlyList<ReadOnlyParallelCommands> TimelineCommands => _timeRunner.ParallelCommands;
|
||||||
|
|
||||||
async partial void OnInitialize()
|
async partial void OnInitialize()
|
||||||
{
|
{
|
||||||
|
_clipboard = App.ServiceProvider.GetService<IClipboard>()!;
|
||||||
|
_timeRunner = App.ServiceProvider.GetService<TimeRunner>()!;
|
||||||
|
_timeRunner.CommandsChanged += (o, e) => OnPropertyChanged(nameof(TimelineCommands));
|
||||||
InitCommandBindings();
|
InitCommandBindings();
|
||||||
|
|
||||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.Up) });
|
_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.Tab) });
|
||||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.PageDown) });
|
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.PageDown) });
|
||||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.PageUp) });
|
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.PageUp) });
|
||||||
|
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.F4, alt: true) });
|
||||||
|
|
||||||
var tab = new Tab();
|
var tab = new Tab();
|
||||||
await tab.Init(LocalContentProvider);
|
await tab.Init(LocalContentProvider);
|
||||||
@@ -176,7 +190,6 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
|
|
||||||
_previousKeys.Clear();
|
_previousKeys.Clear();
|
||||||
PossibleCommands = new();
|
PossibleCommands = new();
|
||||||
FocusDefaultElement?.Invoke();
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@@ -190,7 +203,6 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
AppState.RapidTravelText = "";
|
AppState.RapidTravelText = "";
|
||||||
|
|
||||||
await AppState.SelectedTab.OpenContainer(await AppState.SelectedTab.CurrentLocation.Container.WithoutVirtualContainer(RAPIDTRAVEL));
|
await AppState.SelectedTab.OpenContainer(await AppState.SelectedTab.CurrentLocation.Container.WithoutVirtualContainer(RAPIDTRAVEL));
|
||||||
FocusDefaultElement?.Invoke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SwitchToTab(int number)
|
public async Task SwitchToTab(int number)
|
||||||
@@ -260,7 +272,9 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
{
|
{
|
||||||
if (Inputs != null)
|
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;
|
Inputs = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -270,10 +284,222 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
return Task.CompletedTask;
|
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]
|
[Command]
|
||||||
public void ProcessInputs()
|
public void ProcessInputs()
|
||||||
{
|
{
|
||||||
_inputHandler();
|
_inputHandler?.Invoke();
|
||||||
|
|
||||||
Inputs = null;
|
Inputs = null;
|
||||||
_inputHandler = null;
|
_inputHandler = null;
|
||||||
@@ -286,8 +512,31 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
_inputHandler = null;
|
_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)
|
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;
|
NoCommandFound = false;
|
||||||
|
|
||||||
var isAltPressed = (keyModifiers & KeyModifiers.Alt) == KeyModifiers.Alt;
|
var isAltPressed = (keyModifiers & KeyModifiers.Alt) == KeyModifiers.Alt;
|
||||||
@@ -312,13 +561,12 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
await selectedCommandBinding.InvokeAsync();
|
await selectedCommandBinding.InvokeAsync();
|
||||||
_previousKeys.Clear();
|
_previousKeys.Clear();
|
||||||
PossibleCommands = new();
|
PossibleCommands = new();
|
||||||
|
|
||||||
FocusDefaultElement?.Invoke();
|
|
||||||
}
|
}
|
||||||
else if (_keysToSkip.Any(k => AreKeysEqual(k, _previousKeys)))
|
else if (_keysToSkip.Any(k => AreKeysEqual(k, _previousKeys)))
|
||||||
{
|
{
|
||||||
_previousKeys.Clear();
|
_previousKeys.Clear();
|
||||||
PossibleCommands = new();
|
PossibleCommands = new();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else if (_previousKeys.Count == 2)
|
else if (_previousKeys.Count == 2)
|
||||||
{
|
{
|
||||||
@@ -370,7 +618,11 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
if (selectedCommandBinding != null)
|
if (selectedCommandBinding != null)
|
||||||
{
|
{
|
||||||
await selectedCommandBinding.InvokeAsync();
|
await selectedCommandBinding.InvokeAsync();
|
||||||
FocusDefaultElement?.Invoke();
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,8 +651,6 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
{
|
{
|
||||||
await AppState.SelectedTab.MoveCursorToFirst();
|
await AppState.SelectedTab.MoveCursorToFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusDefaultElement?.Invoke();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -415,7 +665,13 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
|
|
||||||
private void ReadInputs(List<Core.Interactions.InputElement> inputs, Action inputHandler)
|
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;
|
_inputHandler = inputHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,6 +707,11 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
FileTime.App.Core.Command.Commands.CreateContainer,
|
FileTime.App.Core.Command.Commands.CreateContainer,
|
||||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.C),new KeyWithModifiers(Key.C)},
|
new KeyWithModifiers[]{new KeyWithModifiers(Key.C),new KeyWithModifiers(Key.C)},
|
||||||
CreateContainer),
|
CreateContainer),
|
||||||
|
new CommandBinding(
|
||||||
|
"create element",
|
||||||
|
FileTime.App.Core.Command.Commands.CreateElement,
|
||||||
|
new KeyWithModifiers[]{new KeyWithModifiers(Key.C),new KeyWithModifiers(Key.E)},
|
||||||
|
CreateElement),
|
||||||
new CommandBinding(
|
new CommandBinding(
|
||||||
"move to first",
|
"move to first",
|
||||||
FileTime.App.Core.Command.Commands.MoveToTop,
|
FileTime.App.Core.Command.Commands.MoveToTop,
|
||||||
@@ -526,6 +787,66 @@ namespace FileTime.Avalonia.ViewModels
|
|||||||
FileTime.App.Core.Command.Commands.GoToHome,
|
FileTime.App.Core.Command.Commands.GoToHome,
|
||||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.Q)},
|
new KeyWithModifiers[]{new KeyWithModifiers(Key.Q)},
|
||||||
CloseTab),
|
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>()
|
var universalCommandBindings = new List<CommandBinding>()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
Height="18"
|
Height="18"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Source="{SvgImage /Assets/material/folder.svg}" />
|
Source="{Binding Converter={StaticResource ItemToImageConverter}}" />
|
||||||
|
|
||||||
<ItemsControl
|
<ItemsControl
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
|
|||||||
@@ -89,8 +89,29 @@
|
|||||||
</Border>
|
</Border>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid Grid.Column="1" RowDefinitions="40,*,Auto">
|
<Grid Grid.Column="2" RowDefinitions="Auto,40,*,Auto">
|
||||||
<Grid ColumnDefinitions="*,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">
|
<StackPanel Orientation="Horizontal">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
@@ -98,7 +119,7 @@
|
|||||||
Text="{Binding AppState.SelectedTab.TabNumber,StringFormat=({0})}" />
|
Text="{Binding AppState.SelectedTab.TabNumber,StringFormat=({0})}" />
|
||||||
|
|
||||||
<local:PathPresenter Margin="10,5,0,5" DataContext="{Binding AppState.SelectedTab.CurrentLocation.Container.FullName}"/>
|
<local:PathPresenter Margin="10,5,0,5" DataContext="{Binding AppState.SelectedTab.CurrentLocation.Container.FullName}"/>
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="0,5,10,5"
|
Margin="0,5,10,5"
|
||||||
Text="{Binding AppState.SelectedTab.SelectedItem.Item.Name}" Foreground="{StaticResource AccentForegroundBrush}" />
|
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}"/>
|
<local:PathPresenter Margin="5,0,0,0" DataContext="{Binding CurrentLocation.Container.FullName}"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<Rectangle Fill="{DynamicResource ForegroundBrush}" Grid.Row="1" IsVisible="{Binding IsSelected}"/>
|
<Rectangle Fill="{DynamicResource ForegroundBrush}" Grid.Row="1" IsVisible="{Binding IsSelected}"/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
@@ -132,7 +153,7 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
Grid.Row="1"
|
Grid.Row="2"
|
||||||
Margin="20,0,0,0">
|
Margin="20,0,0,0">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
@@ -146,8 +167,8 @@
|
|||||||
<Grid>
|
<Grid>
|
||||||
<ListBox
|
<ListBox
|
||||||
Classes="ContentListView"
|
Classes="ContentListView"
|
||||||
Items="{Binding AppState.SelectedTab.Parent.Items}"
|
IsEnabled="False"
|
||||||
SelectedItem="{Binding AppState.SelectedTab.CurrentLocation}">
|
Items="{Binding AppState.SelectedTab.Parent.Items}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<local:ItemView ShowAttributes="False"/>
|
<local:ItemView ShowAttributes="False"/>
|
||||||
@@ -202,6 +223,7 @@
|
|||||||
<Grid Grid.Column="4">
|
<Grid Grid.Column="4">
|
||||||
<ListBox
|
<ListBox
|
||||||
Classes="ContentListView"
|
Classes="ContentListView"
|
||||||
|
IsEnabled="False"
|
||||||
x:Name="ChildItems"
|
x:Name="ChildItems"
|
||||||
Items="{Binding AppState.SelectedTab.ChildContainer.Items}"
|
Items="{Binding AppState.SelectedTab.ChildContainer.Items}"
|
||||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||||
@@ -223,9 +245,78 @@
|
|||||||
</TextBlock>
|
</TextBlock>
|
||||||
</Grid>
|
</Grid>
|
||||||
</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 Grid.Row="2">
|
<Grid Grid.Row="3">
|
||||||
<Grid IsVisible="{Binding AppState.ViewMode, Converter={StaticResource EqualityConverter}, ConverterParameter=RapidTravel}">
|
<Grid IsVisible="{Binding AppState.ViewMode, Converter={StaticResource EqualityConverter}, ConverterParameter=RapidTravel}">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="1" />
|
<RowDefinition Height="1" />
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
using Avalonia.Markup.Xaml;
|
using Avalonia.Markup.Xaml;
|
||||||
|
using FileTime.Avalonia.Misc;
|
||||||
using FileTime.Avalonia.ViewModels;
|
using FileTime.Avalonia.ViewModels;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace FileTime.Avalonia.Views
|
namespace FileTime.Avalonia.Views
|
||||||
{
|
{
|
||||||
@@ -15,21 +18,13 @@ namespace FileTime.Avalonia.Views
|
|||||||
{
|
{
|
||||||
if (value != DataContext)
|
if (value != DataContext)
|
||||||
{
|
{
|
||||||
if (DataContext is MainPageViewModel currentViewModel)
|
|
||||||
{
|
|
||||||
currentViewModel.FocusDefaultElement = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
DataContext = value;
|
DataContext = value;
|
||||||
|
|
||||||
if (value != null)
|
|
||||||
{
|
|
||||||
//value.FocusDefaultElement = () => this.FindControl<ListBox>("CurrentItems")?.Focus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private InputElementWrapper? _inputElementWrapper;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -45,12 +40,58 @@ namespace FileTime.Avalonia.Views
|
|||||||
|
|
||||||
public async void OnKeyDown(object sender, KeyEventArgs e)
|
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)
|
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.Command;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.StateManagement;
|
using FileTime.Core.StateManagement;
|
||||||
|
using FileTime.Core.Timeline;
|
||||||
|
|
||||||
namespace FileTime.Providers.Local.CommandHandlers
|
namespace FileTime.Providers.Local.CommandHandlers
|
||||||
{
|
{
|
||||||
public class CopyCommandHandler : ICommandHandler
|
public class CopyCommandHandler : ICommandHandler
|
||||||
{
|
{
|
||||||
private readonly List<Thread> _copyOperations = new();
|
|
||||||
private readonly ElementCreationStates _elementCreationStates;
|
private readonly ElementCreationStates _elementCreationStates;
|
||||||
|
|
||||||
public CopyCommandHandler(ElementCreationStates elementCreationStates)
|
public CopyCommandHandler(ElementCreationStates elementCreationStates)
|
||||||
@@ -25,17 +25,14 @@ namespace FileTime.Providers.Local.CommandHandlers
|
|||||||
return true;
|
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()}'.");
|
if (command is not CopyCommand copyCommand) throw new ArgumentException($"Can not execute command of type '{command.GetType()}'.");
|
||||||
|
|
||||||
var thread = new Thread(() => copyCommand.Execute(CopyElement));
|
await copyCommand.Execute(CopyElement, timeRunner);
|
||||||
thread.Start();
|
|
||||||
|
|
||||||
_copyOperations.Add(thread);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyElement(IAbsolutePath sourcePath, IAbsolutePath targetPath)
|
public static void CopyElement(AbsolutePath sourcePath, AbsolutePath targetPath)
|
||||||
{
|
{
|
||||||
using var sourceStream = File.OpenRead(sourcePath.Path);
|
using var sourceStream = File.OpenRead(sourcePath.Path);
|
||||||
using var sourceReader = new BinaryReader(sourceStream);
|
using var sourceReader = new BinaryReader(sourceStream);
|
||||||
@@ -43,7 +40,7 @@ namespace FileTime.Providers.Local.CommandHandlers
|
|||||||
using var targetStream = File.OpenWrite(targetPath.Path);
|
using var targetStream = File.OpenWrite(targetPath.Path);
|
||||||
using var targetWriter = new BinaryWriter(targetStream);
|
using var targetWriter = new BinaryWriter(targetStream);
|
||||||
|
|
||||||
var bufferSize = 1024 * 1024;
|
const int bufferSize = 1024 * 1024;
|
||||||
byte[] dataRead;
|
byte[] dataRead;
|
||||||
|
|
||||||
do
|
do
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using AsyncEvent;
|
using AsyncEvent;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
@@ -19,12 +20,15 @@ namespace FileTime.Providers.Local
|
|||||||
|
|
||||||
public string? FullName { get; }
|
public string? FullName { get; }
|
||||||
public bool IsHidden => false;
|
public bool IsHidden => false;
|
||||||
|
public bool IsLoaded => true;
|
||||||
|
|
||||||
public IContentProvider Provider => this;
|
public IContentProvider Provider => this;
|
||||||
|
|
||||||
public AsyncEventHandler Refreshed { get; } = new();
|
public AsyncEventHandler Refreshed { get; } = new();
|
||||||
|
|
||||||
public bool IsCaseInsensitive { get; }
|
public bool IsCaseInsensitive { get; }
|
||||||
|
public bool CanDelete => false;
|
||||||
|
public bool CanRename => false;
|
||||||
|
|
||||||
public LocalContentProvider(ILogger<LocalContentProvider> logger)
|
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<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult(_items);
|
||||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IContainer>?)_rootContainers);
|
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<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 string Attributes => GetAttributes();
|
||||||
|
|
||||||
public DateTime CreatedAt => File.CreationTime;
|
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;
|
File = file;
|
||||||
|
|
||||||
Name = file.Name;
|
Name = file.Name;
|
||||||
FullName = file.FullName;
|
FullName = parent.FullName + Constants.SeparatorChar + file.Name;
|
||||||
Provider = contentProvider;
|
Provider = contentProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +46,14 @@ namespace FileTime.Providers.Local
|
|||||||
File.Delete();
|
File.Delete();
|
||||||
return Task.CompletedTask;
|
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()
|
public string GetAttributes()
|
||||||
{
|
{
|
||||||
@@ -57,5 +70,7 @@ namespace FileTime.Providers.Local
|
|||||||
+ ((File.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-");
|
+ ((File.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IContainer? GetParent() => _parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,12 @@ namespace FileTime.Providers.Local
|
|||||||
|
|
||||||
public string FullName { get; }
|
public string FullName { get; }
|
||||||
|
|
||||||
|
public bool IsLoaded => _items != null;
|
||||||
|
public bool CanDelete => true;
|
||||||
|
public bool CanRename => true;
|
||||||
|
|
||||||
public AsyncEventHandler Refreshed { get; } = new();
|
public AsyncEventHandler Refreshed { get; } = new();
|
||||||
|
|
||||||
public string Attributes => GetAttributes();
|
public string Attributes => GetAttributes();
|
||||||
|
|
||||||
public DateTime CreatedAt => Directory.CreationTime;
|
public DateTime CreatedAt => Directory.CreationTime;
|
||||||
@@ -48,7 +53,7 @@ namespace FileTime.Providers.Local
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_containers = Directory.GetDirectories().Select(d => new LocalFolder(d, Provider, this)).OrderBy(d => d.Name).ToList().AsReadOnly();
|
_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 { }
|
catch { }
|
||||||
|
|
||||||
@@ -115,6 +120,14 @@ namespace FileTime.Providers.Local
|
|||||||
Directory.Delete(true);
|
Directory.Delete(true);
|
||||||
return Task.CompletedTask;
|
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()
|
public string GetAttributes()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using AsyncEvent;
|
using AsyncEvent;
|
||||||
using FileTime.Core.Interactions;
|
using FileTime.Core.Interactions;
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
@@ -11,7 +12,7 @@ namespace FileTime.Providers.Smb
|
|||||||
private readonly IInputInterface _inputInterface;
|
private readonly IInputInterface _inputInterface;
|
||||||
private readonly List<IContainer> _rootContainers;
|
private readonly List<IContainer> _rootContainers;
|
||||||
private readonly IReadOnlyList<IContainer> _rootContainersReadOnly;
|
private readonly IReadOnlyList<IContainer> _rootContainersReadOnly;
|
||||||
private readonly IReadOnlyList<IItem>? _items;
|
private IReadOnlyList<IItem>? _items;
|
||||||
private readonly IReadOnlyList<IElement>? _elements = new List<IElement>().AsReadOnly();
|
private readonly IReadOnlyList<IElement>? _elements = new List<IElement>().AsReadOnly();
|
||||||
|
|
||||||
public string Name { get; } = "smb";
|
public string Name { get; } = "smb";
|
||||||
@@ -19,14 +20,18 @@ namespace FileTime.Providers.Smb
|
|||||||
public string? FullName { get; }
|
public string? FullName { get; }
|
||||||
|
|
||||||
public bool IsHidden => false;
|
public bool IsHidden => false;
|
||||||
|
public bool IsLoaded => true;
|
||||||
|
|
||||||
public IContentProvider Provider => this;
|
public IContentProvider Provider => this;
|
||||||
|
public bool CanDelete => false;
|
||||||
|
public bool CanRename => false;
|
||||||
|
|
||||||
public AsyncEventHandler Refreshed { get; } = new();
|
public AsyncEventHandler Refreshed { get; } = new();
|
||||||
|
|
||||||
public SmbContentProvider(IInputInterface inputInterface)
|
public SmbContentProvider(IInputInterface inputInterface)
|
||||||
{
|
{
|
||||||
_rootContainers = new List<IContainer>();
|
_rootContainers = new List<IContainer>();
|
||||||
|
_items = new List<IItem>();
|
||||||
_rootContainersReadOnly = _rootContainers.AsReadOnly();
|
_rootContainersReadOnly = _rootContainers.AsReadOnly();
|
||||||
_inputInterface = inputInterface;
|
_inputInterface = inputInterface;
|
||||||
}
|
}
|
||||||
@@ -40,6 +45,7 @@ namespace FileTime.Providers.Smb
|
|||||||
{
|
{
|
||||||
container = new SmbServer(fullName, this, _inputInterface);
|
container = new SmbServer(fullName, this, _inputInterface);
|
||||||
_rootContainers.Add(container);
|
_rootContainers.Add(container);
|
||||||
|
_items = _rootContainers.OrderBy(c => c.Name).ToList().AsReadOnly();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Refresh();
|
await Refresh();
|
||||||
@@ -78,5 +84,7 @@ namespace FileTime.Providers.Smb
|
|||||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult(_items);
|
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<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult((IReadOnlyList<IContainer>?)_rootContainersReadOnly);
|
||||||
public Task<IReadOnlyList<IElement>?> GetElements(CancellationToken token = default) => Task.FromResult(_elements);
|
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 string? FullName { get; }
|
||||||
|
|
||||||
public bool IsHidden => false;
|
public bool IsHidden => false;
|
||||||
|
public bool CanDelete => true;
|
||||||
|
public bool CanRename => true;
|
||||||
|
|
||||||
public IContentProvider Provider { get; }
|
public IContentProvider Provider { get; }
|
||||||
|
private IContainer _parent;
|
||||||
|
|
||||||
public SmbFile(string name, SmbContentProvider provider, IContainer parent)
|
public SmbFile(string name, SmbContentProvider provider, IContainer parent)
|
||||||
{
|
{
|
||||||
@@ -22,16 +25,23 @@ namespace FileTime.Providers.Smb
|
|||||||
FullName = parent.FullName + Constants.SeparatorChar + Name;
|
FullName = parent.FullName + Constants.SeparatorChar + Name;
|
||||||
|
|
||||||
Provider = provider;
|
Provider = provider;
|
||||||
|
_parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task Delete()
|
public Task Delete()
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
public Task Rename(string newName)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public string GetPrimaryAttributeText()
|
public string GetPrimaryAttributeText()
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IContainer? GetParent() => _parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,9 +19,12 @@ namespace FileTime.Providers.Smb
|
|||||||
public string? FullName { get; }
|
public string? FullName { get; }
|
||||||
|
|
||||||
public bool IsHidden => false;
|
public bool IsHidden => false;
|
||||||
|
public bool IsLoaded => _items != null;
|
||||||
|
|
||||||
public SmbContentProvider Provider { get; }
|
public SmbContentProvider Provider { get; }
|
||||||
IContentProvider IItem.Provider => Provider;
|
IContentProvider IItem.Provider => Provider;
|
||||||
|
public bool CanDelete => true;
|
||||||
|
public bool CanRename => true;
|
||||||
|
|
||||||
public AsyncEventHandler Refreshed { get; } = new();
|
public AsyncEventHandler Refreshed { get; } = new();
|
||||||
|
|
||||||
@@ -77,6 +80,10 @@ namespace FileTime.Providers.Smb
|
|||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
public Task Rename(string newName)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
public async Task Refresh()
|
public async Task Refresh()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,10 +24,13 @@ namespace FileTime.Providers.Smb
|
|||||||
public string? FullName { get; }
|
public string? FullName { get; }
|
||||||
|
|
||||||
public bool IsHidden => false;
|
public bool IsHidden => false;
|
||||||
|
public bool IsLoaded => _items != null;
|
||||||
|
|
||||||
public SmbContentProvider Provider { get; }
|
public SmbContentProvider Provider { get; }
|
||||||
|
|
||||||
IContentProvider IItem.Provider => Provider;
|
IContentProvider IItem.Provider => Provider;
|
||||||
|
public bool CanDelete => false;
|
||||||
|
public bool CanRename => false;
|
||||||
|
|
||||||
public AsyncEventHandler Refreshed { get; } = new();
|
public AsyncEventHandler Refreshed { get; } = new();
|
||||||
|
|
||||||
@@ -128,5 +131,7 @@ namespace FileTime.Providers.Smb
|
|||||||
}
|
}
|
||||||
return _client;
|
return _client;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task Rename(string newName) => throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using AsyncEvent;
|
using AsyncEvent;
|
||||||
using FileTime.Core.Interactions;
|
|
||||||
using FileTime.Core.Models;
|
using FileTime.Core.Models;
|
||||||
using FileTime.Core.Providers;
|
using FileTime.Core.Providers;
|
||||||
using SMBLibrary;
|
using SMBLibrary;
|
||||||
@@ -20,9 +19,12 @@ namespace FileTime.Providers.Smb
|
|||||||
public string? FullName { get; }
|
public string? FullName { get; }
|
||||||
|
|
||||||
public bool IsHidden => false;
|
public bool IsHidden => false;
|
||||||
|
public bool IsLoaded => _items != null;
|
||||||
|
|
||||||
public SmbContentProvider Provider { get; }
|
public SmbContentProvider Provider { get; }
|
||||||
IContentProvider IItem.Provider => Provider;
|
IContentProvider IItem.Provider => Provider;
|
||||||
|
public bool CanDelete => false;
|
||||||
|
public bool CanRename => false;
|
||||||
|
|
||||||
public AsyncEventHandler Refreshed { get; } = new();
|
public AsyncEventHandler Refreshed { get; } = new();
|
||||||
|
|
||||||
@@ -150,5 +152,7 @@ namespace FileTime.Providers.Smb
|
|||||||
|
|
||||||
return (containers, elements);
|
return (containers, elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task Rename(string newName) => throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user