311 lines
12 KiB
C#
311 lines
12 KiB
C#
using AsyncEvent;
|
|
using FileTime.Core.Models;
|
|
|
|
namespace FileTime.Core.Components
|
|
{
|
|
public class Tab
|
|
{
|
|
private IItem? _currentSelectedItem;
|
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
private IContainer _currentLocation;
|
|
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
private string? _lastPath;
|
|
|
|
private bool _currentlySelecting = false;
|
|
|
|
private readonly object _guardSetCurrentSelectedItemCTS = new();
|
|
private CancellationTokenSource? _setCurrentSelectedItemCTS;
|
|
|
|
public int CurrentSelectedIndex { get; private set; }
|
|
|
|
public AsyncEventHandler CurrentLocationChanged = new();
|
|
public AsyncEventHandler CurrentSelectedItemChanged = new();
|
|
|
|
public async Task Init(IContainer currentPath)
|
|
{
|
|
await SetCurrentLocation(currentPath);
|
|
}
|
|
|
|
public Task<IContainer> GetCurrentLocation(CancellationToken token = default)
|
|
{
|
|
return Task.FromResult(_currentLocation);
|
|
}
|
|
|
|
public async Task SetCurrentLocation(IContainer value)
|
|
{
|
|
if (_currentLocation != value)
|
|
{
|
|
if (_currentLocation != null)
|
|
{
|
|
_currentLocation.Refreshed.Remove(HandleCurrentLocationRefresh);
|
|
}
|
|
|
|
_currentLocation = value;
|
|
await CurrentLocationChanged.InvokeAsync(this, AsyncEventArgs.Empty);
|
|
|
|
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
|
await SetCurrentSelectedItem(await GetItemByLastPath() ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null));
|
|
_currentLocation.Refreshed.Add(HandleCurrentLocationRefresh);
|
|
}
|
|
}
|
|
|
|
public Task<IItem?> GetCurrentSelectedItem()
|
|
{
|
|
return Task.FromResult(_currentSelectedItem);
|
|
}
|
|
|
|
public async Task SetCurrentSelectedItem(IItem? value, bool secondary = false)
|
|
{
|
|
if (_currentlySelecting) return;
|
|
|
|
if (_currentSelectedItem != value)
|
|
{
|
|
IItem? itemToSelect = null;
|
|
if (value != null)
|
|
{
|
|
itemToSelect = (await _currentLocation.GetItems())?.FirstOrDefault(i =>
|
|
i.FullName == null && value?.FullName == null
|
|
? i.Name == value?.Name
|
|
: i.FullName == value?.FullName);
|
|
if (itemToSelect == null) throw new IndexOutOfRangeException($"Provided item ({value.FullName ?? "unknwon"}) does not exists in the current container ({_currentLocation.FullName ?? "unknwon"}).");
|
|
}
|
|
|
|
CancellationToken newToken;
|
|
lock (_guardSetCurrentSelectedItemCTS)
|
|
{
|
|
_setCurrentSelectedItemCTS?.Cancel();
|
|
_setCurrentSelectedItemCTS = new CancellationTokenSource();
|
|
newToken = _setCurrentSelectedItemCTS.Token;
|
|
}
|
|
|
|
_currentSelectedItem = itemToSelect;
|
|
_lastPath = GetCommonPath(_lastPath, itemToSelect?.FullName);
|
|
|
|
var newCurrentSelectedIndex = await GetItemIndex(itemToSelect, newToken);
|
|
if (newToken.IsCancellationRequested) return;
|
|
CurrentSelectedIndex = newCurrentSelectedIndex;
|
|
|
|
await CurrentSelectedItemChanged.InvokeAsync(this, AsyncEventArgs.Empty, newToken);
|
|
}
|
|
}
|
|
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?.Split(Constants.SeparatorChar).Length) ?? 0)
|
|
.FirstOrDefault();
|
|
|
|
return (await container.GetItems())?.FirstOrDefault(i => i.Name == itemNameToSelect);
|
|
}
|
|
|
|
private static string GetCommonPath(string? oldPath, string? newPath)
|
|
{
|
|
var oldPathParts = oldPath?.Split(Constants.SeparatorChar) ?? Array.Empty<string>();
|
|
var newPathParts = newPath?.Split(Constants.SeparatorChar) ?? Array.Empty<string>();
|
|
|
|
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, CancellationToken token = default)
|
|
{
|
|
var currentSelectedName = (await GetCurrentSelectedItem())?.FullName ?? (await GetItemByLastPath())?.FullName;
|
|
var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!;
|
|
if (currentSelectedName != null)
|
|
{
|
|
await SetCurrentSelectedItem(currentLocationItems.FirstOrDefault(i => i.FullName == currentSelectedName) ?? currentLocationItems[0]);
|
|
}
|
|
else if (currentLocationItems.Count > 0)
|
|
{
|
|
await SetCurrentSelectedItem(currentLocationItems[0]);
|
|
}
|
|
}
|
|
|
|
public async Task SelectFirstItem()
|
|
{
|
|
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
|
if (currentLocationItems.Count > 0)
|
|
{
|
|
await SetCurrentSelectedItem(currentLocationItems[0]);
|
|
}
|
|
}
|
|
|
|
public async Task SelectLastItem()
|
|
{
|
|
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
|
if (currentLocationItems.Count > 0)
|
|
{
|
|
await SetCurrentSelectedItem(currentLocationItems[currentLocationItems.Count - 1]);
|
|
}
|
|
}
|
|
|
|
public async Task SelectPreviousItem(int skip = 0)
|
|
{
|
|
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
|
var possibleItemsToSelect = currentLocationItems.Take(CurrentSelectedIndex).Reverse().Skip(skip).ToList();
|
|
|
|
if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = currentLocationItems.ToList();
|
|
await SelectItem(possibleItemsToSelect);
|
|
}
|
|
|
|
public async Task SelectNextItem(int skip = 0)
|
|
{
|
|
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
|
var possibleItemsToSelect = currentLocationItems.Skip(CurrentSelectedIndex + 1 + skip).ToList();
|
|
|
|
if (possibleItemsToSelect.Count == 0) possibleItemsToSelect = currentLocationItems.Reverse().ToList();
|
|
await SelectItem(possibleItemsToSelect);
|
|
}
|
|
|
|
private async Task SelectItem(IEnumerable<IItem> currentPossibleItems)
|
|
{
|
|
if (!currentPossibleItems.Any()) return;
|
|
|
|
var currentLocation = await GetCurrentLocation();
|
|
var currentLocationItems = (await currentLocation.GetItems())!;
|
|
|
|
if (await GetCurrentSelectedItem() != null)
|
|
{
|
|
_currentlySelecting = true;
|
|
currentLocation?.RefreshAsync();
|
|
|
|
IItem? newSelectedItem = null;
|
|
foreach (var item in currentPossibleItems)
|
|
{
|
|
if (currentLocationItems.FirstOrDefault(i => i.Name == item.Name) is var possibleNewSelectedItem
|
|
&& possibleNewSelectedItem is not null)
|
|
{
|
|
newSelectedItem = possibleNewSelectedItem;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (newSelectedItem != null)
|
|
{
|
|
newSelectedItem = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == newSelectedItem.Name);
|
|
}
|
|
|
|
_currentlySelecting = false;
|
|
await SetCurrentSelectedItem(newSelectedItem ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null));
|
|
}
|
|
else
|
|
{
|
|
await SetCurrentSelectedItem(currentLocationItems.Count > 0 ? currentLocationItems[0] : null);
|
|
}
|
|
}
|
|
|
|
public async Task GoToProvider()
|
|
{
|
|
var currentLocatin = await GetCurrentLocation();
|
|
if (currentLocatin == null) return;
|
|
|
|
await SetCurrentLocation(currentLocatin.Provider);
|
|
}
|
|
|
|
public async Task GoToRoot()
|
|
{
|
|
var currentLocatin = await GetCurrentLocation();
|
|
if (currentLocatin == null) return;
|
|
|
|
var root = currentLocatin;
|
|
while (root!.GetParent() != null)
|
|
{
|
|
root = root.GetParent();
|
|
}
|
|
|
|
await SetCurrentLocation(root);
|
|
}
|
|
|
|
public async Task GoUp()
|
|
{
|
|
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
|
var lastCurrentLocation = await GetCurrentLocation();
|
|
var parent = (await GetCurrentLocation()).GetParent();
|
|
|
|
if (parent is not null)
|
|
{
|
|
if (lastCurrentLocation is VirtualContainer lastCurrentVirtualContainer)
|
|
{
|
|
await SetCurrentLocation(lastCurrentVirtualContainer.CloneVirtualChainFor(parent, v => v.IsPermanent));
|
|
await SetCurrentSelectedItem(lastCurrentVirtualContainer.GetRealContainer());
|
|
}
|
|
else
|
|
{
|
|
await SetCurrentLocation(parent);
|
|
var newCurrentLocation = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == lastCurrentLocation.Name);
|
|
await SetCurrentSelectedItem(newCurrentLocation);
|
|
}
|
|
|
|
foreach(var lastLocationItem in currentLocationItems.OfType<IContainer>())
|
|
{
|
|
lastLocationItem.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task Open()
|
|
{
|
|
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
|
if (_currentSelectedItem is IContainer childContainer)
|
|
{
|
|
if (await GetCurrentLocation() is VirtualContainer currentVirtuakContainer)
|
|
{
|
|
await SetCurrentLocation(currentVirtuakContainer.CloneVirtualChainFor(childContainer, v => v.IsPermanent));
|
|
}
|
|
else
|
|
{
|
|
await SetCurrentLocation(childContainer);
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task OpenContainer(IContainer container) => await SetCurrentLocation(container);
|
|
|
|
private async Task<int> GetItemIndex(IItem? item, CancellationToken token)
|
|
{
|
|
if (item == null) return -1;
|
|
var currentLocationItems = (await (await GetCurrentLocation(token)).GetItems(token))!;
|
|
|
|
if (token.IsCancellationRequested) return -1;
|
|
for (var i = 0; i < currentLocationItems.Count; i++)
|
|
{
|
|
if (currentLocationItems[i] == item) return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
} |