Optimization, UI improvements, Run command
This commit is contained in:
@@ -101,7 +101,7 @@ namespace FileTime.App.Core.Tab
|
||||
: new List<AbsolutePath>().AsReadOnly();
|
||||
}
|
||||
|
||||
public async Task MakrCurrentItem()
|
||||
public async Task MarkCurrentItem()
|
||||
{
|
||||
var currentLocation = await Tab!.GetCurrentLocation();
|
||||
if (currentLocation != null)
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace FileTime.ConsoleUI.App
|
||||
{
|
||||
if (_selectedTab != null)
|
||||
{
|
||||
await _tabStates[_selectedTab].MakrCurrentItem();
|
||||
await _tabStates[_selectedTab].MarkCurrentItem();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace FileTime.Core.Command
|
||||
if (itemToRename != null)
|
||||
{
|
||||
await itemToRename.Rename(Target);
|
||||
timeRunner.RefreshContainer?.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!));
|
||||
if(timeRunner.RefreshContainer != null) await timeRunner.RefreshContainer.InvokeAsync(this, new AbsolutePath(itemToRename.GetParent()!));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@ namespace FileTime.Core.Components
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
||||
private string? _lastPath;
|
||||
|
||||
private readonly object _guardSetCurrentSelectedItemCTS = new object();
|
||||
private bool _currentlySelecting = false;
|
||||
|
||||
private readonly object _guardSetCurrentSelectedItemCTS = new();
|
||||
private CancellationTokenSource? _setCurrentSelectedItemCTS;
|
||||
|
||||
public int CurrentSelectedIndex { get; private set; }
|
||||
@@ -54,6 +56,8 @@ namespace FileTime.Core.Components
|
||||
|
||||
public async Task SetCurrentSelectedItem(IItem? value, bool secondary = false)
|
||||
{
|
||||
if (_currentlySelecting) return;
|
||||
|
||||
if (_currentSelectedItem != value)
|
||||
{
|
||||
IItem? itemToSelect = null;
|
||||
@@ -190,11 +194,13 @@ namespace FileTime.Core.Components
|
||||
{
|
||||
if (!currentPossibleItems.Any()) return;
|
||||
|
||||
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!;
|
||||
var currentLocation = await GetCurrentLocation();
|
||||
var currentLocationItems = (await currentLocation.GetItems())!;
|
||||
|
||||
if (await GetCurrentSelectedItem() != null)
|
||||
{
|
||||
(await GetCurrentLocation())?.RefreshAsync();
|
||||
_currentlySelecting = true;
|
||||
currentLocation?.RefreshAsync();
|
||||
|
||||
IItem? newSelectedItem = null;
|
||||
foreach (var item in currentPossibleItems)
|
||||
@@ -212,6 +218,7 @@ namespace FileTime.Core.Components
|
||||
newSelectedItem = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == newSelectedItem.Name);
|
||||
}
|
||||
|
||||
_currentlySelecting = false;
|
||||
await SetCurrentSelectedItem(newSelectedItem ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null));
|
||||
}
|
||||
else
|
||||
@@ -261,6 +268,11 @@ namespace FileTime.Core.Components
|
||||
var newCurrentLocation = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == lastCurrentLocation.Name);
|
||||
await SetCurrentSelectedItem(newCurrentLocation);
|
||||
}
|
||||
|
||||
foreach(var lastLocationItem in currentLocationItems.OfType<IContainer>())
|
||||
{
|
||||
lastLocationItem.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,5 +4,7 @@ namespace FileTime.Core.Models
|
||||
{
|
||||
bool IsSpecial { get; }
|
||||
string GetPrimaryAttributeText();
|
||||
Task<string> GetContent(CancellationToken token = default);
|
||||
Task<long> GetElementSize(CancellationToken token = default);
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,12 @@ using FileTime.Core.Providers;
|
||||
|
||||
namespace FileTime.Core.Models
|
||||
{
|
||||
public interface IItem
|
||||
public interface IItem : IDisposable
|
||||
{
|
||||
string Name { get; }
|
||||
string? FullName { get; }
|
||||
bool IsHidden { get; }
|
||||
bool IsDisposed { get; }
|
||||
SupportsDelete CanDelete { get; }
|
||||
bool CanRename { get; }
|
||||
IContentProvider Provider { get; }
|
||||
|
||||
@@ -35,6 +35,8 @@ namespace FileTime.Core.Models
|
||||
|
||||
public AsyncEventHandler Refreshed { get; }
|
||||
|
||||
public bool IsDisposed => BaseContainer.IsDisposed;
|
||||
|
||||
private void RefreshAddBase(Func<object?, AsyncEventArgs, CancellationToken, Task> handler)
|
||||
{
|
||||
BaseContainer.Refreshed.Add(handler);
|
||||
@@ -168,5 +170,10 @@ namespace FileTime.Core.Models
|
||||
|
||||
public async Task Rename(string newName) => await BaseContainer.Rename(newName);
|
||||
public async Task<bool> CanOpen() => await BaseContainer.CanOpen();
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
BaseContainer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ namespace FileTime.Core.Providers
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public bool IsDisposed => false;
|
||||
|
||||
public TopContainer(IEnumerable<IContentProvider> contentProviders)
|
||||
{
|
||||
_contentProviders = new List<IContentProvider>(contentProviders);
|
||||
@@ -57,7 +59,7 @@ namespace FileTime.Core.Providers
|
||||
|
||||
public Task<bool> IsExists(string name) => throw new NotImplementedException();
|
||||
|
||||
public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
public async Task RefreshAsync(CancellationToken token = default) => await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
|
||||
public Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult(_items);
|
||||
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult(_containers);
|
||||
@@ -68,5 +70,7 @@ namespace FileTime.Core.Providers
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
|
||||
public Task<bool> CanOpen() => Task.FromResult(true);
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ namespace FileTime.Core.Timeline
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
|
||||
{
|
||||
_parent = parent;
|
||||
@@ -121,5 +123,7 @@ namespace FileTime.Core.Timeline
|
||||
return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider);
|
||||
}
|
||||
public Task<bool> CanOpen() => Task.FromResult(true);
|
||||
|
||||
public void Dispose() => IsDisposed = true;
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ namespace FileTime.Core.Timeline
|
||||
public IContentProvider Provider { get; }
|
||||
public IContentProvider VirtualProvider { get; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public Task Delete(bool hardDelete = false) => Task.CompletedTask;
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
@@ -38,5 +40,10 @@ namespace FileTime.Core.Timeline
|
||||
public string GetPrimaryAttributeText() => "";
|
||||
|
||||
public Task Rename(string newName) => Task.CompletedTask;
|
||||
|
||||
public Task<string> GetContent(CancellationToken token = default) => Task.FromResult("");
|
||||
public Task<long> GetElementSize(CancellationToken token = default) => Task.FromResult(-1L);
|
||||
|
||||
public void Dispose() => IsDisposed = true;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ namespace FileTime.Core.Timeline
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public bool IsDisposed => false;
|
||||
|
||||
public TimeProvider(PointInTime pointInTime)
|
||||
{
|
||||
_pointInTime = pointInTime;
|
||||
@@ -90,5 +92,7 @@ namespace FileTime.Core.Timeline
|
||||
|
||||
public void SetParent(IContainer container) { }
|
||||
public Task<bool> CanOpen() => Task.FromResult(true);
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
<Color x:Key="AppBackgroundColor">#E7073642</Color>
|
||||
<Color x:Key="ContainerBackgroundColor">#083e4c</Color>
|
||||
<Color x:Key="TransparentContainerBackgroundColor">#D0083e4c</Color>
|
||||
<Color x:Key="BarelyTransparentBackgroundColor">#80083e4c</Color>
|
||||
|
||||
<Color x:Key="ItemBackgroundColor">#00000000</Color>
|
||||
<Color x:Key="AlternativeItemBackgroundColor">#10000000</Color>
|
||||
@@ -128,6 +129,7 @@
|
||||
<converters:IsEmptyConverter x:Key="IsNotEmptyConverter" Inverse="true"/>
|
||||
<converters:ExceptionToStringConverter x:Key="ExceptionToStringConverter"/>
|
||||
<converters:BoolInverter x:Key="BoolInverter"/>
|
||||
<converters:IsElementConverter x:Key="IsElementConverter"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
@@ -145,6 +147,10 @@
|
||||
<Style Selector="TextBlock.ExtraSmallText">
|
||||
<Setter Property="FontSize" Value="11"/>
|
||||
</Style>
|
||||
<Style Selector="TextBox">
|
||||
<Setter Property="Foreground" Value="{DynamicResource ForegroundBrush}"/>
|
||||
<Setter Property="Background" Value="{DynamicResource ContainerBackgroundBrush}"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox.ContentListView">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace FileTime.Avalonia.Application
|
||||
[Inject(typeof(Tab))]
|
||||
public partial class TabContainer : INewItemProcessor
|
||||
{
|
||||
private bool _updateFromCode;
|
||||
[Property]
|
||||
private TabState _tabState;
|
||||
|
||||
@@ -46,28 +47,44 @@ namespace FileTime.Avalonia.Application
|
||||
get => _selectedItem;
|
||||
set
|
||||
{
|
||||
if (value != null)
|
||||
if (!_updateFromCode && value != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
SetSelectedItemAsync(value, true).Wait();
|
||||
/*var task = SetSelectedItemAsync(value, true);
|
||||
Task.WaitAll(new Task[] { task }, 100);*/
|
||||
SetSelectedItemAsync(value, true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
catch
|
||||
{
|
||||
//TODO: Debug, linux start after restore 3 tabs
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Property]
|
||||
private ElementPreviewViewModel? _elementPreview;
|
||||
|
||||
public async Task SetSelectedItemAsync(IItemViewModel? value, bool fromDataBinding = false)
|
||||
{
|
||||
if (_selectedItem != value)
|
||||
{
|
||||
_selectedItem = value;
|
||||
|
||||
OnPropertyChanged(nameof(SelectedItem));
|
||||
if (value is ElementViewModel elementViewModel)
|
||||
{
|
||||
var elementPreview = new ElementPreviewViewModel();
|
||||
await elementPreview.Init(elementViewModel.Element);
|
||||
ElementPreview = elementPreview;
|
||||
}
|
||||
else
|
||||
{
|
||||
ElementPreview = null;
|
||||
}
|
||||
|
||||
await Tab.SetCurrentSelectedItem(SelectedItem?.Item, fromDataBinding);
|
||||
OnPropertyChanged(nameof(SelectedItem));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +191,7 @@ namespace FileTime.Avalonia.Application
|
||||
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
var items = await _currentLocation.GetItems();
|
||||
var items = await _currentLocation.GetItems(token);
|
||||
if (items?.Count > 0)
|
||||
{
|
||||
foreach (var item in items)
|
||||
@@ -198,7 +215,7 @@ namespace FileTime.Avalonia.Application
|
||||
{
|
||||
if (token.IsCancellationRequested) return;
|
||||
var activeChildItem = await Tab.GetItemByLastPath(containerViewModel.Container);
|
||||
child = (await containerViewModel.GetItems()).FirstOrDefault(i => i.Item == activeChildItem);
|
||||
child = (await containerViewModel.GetItems(token)).FirstOrDefault(i => i.Item == activeChildItem);
|
||||
if (child != null)
|
||||
{
|
||||
child.IsSelected = true;
|
||||
@@ -239,59 +256,71 @@ namespace FileTime.Avalonia.Application
|
||||
catch { }
|
||||
}
|
||||
|
||||
private async Task RunFromCode(Func<Task> task)
|
||||
{
|
||||
_updateFromCode = true;
|
||||
try
|
||||
{
|
||||
await task();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_updateFromCode = false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Open()
|
||||
{
|
||||
if (ChildContainer != null)
|
||||
{
|
||||
await Tab.Open();
|
||||
await UpdateCurrentSelectedItem();
|
||||
await RunFromCode(Tab.Open);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task GoUp()
|
||||
{
|
||||
await Tab.GoUp();
|
||||
await UpdateCurrentSelectedItem();
|
||||
await RunFromCode(Tab.GoUp);
|
||||
}
|
||||
|
||||
public async Task MoveCursorDown()
|
||||
{
|
||||
await Tab.SelectNextItem();
|
||||
await RunFromCode(async () => await Tab.SelectNextItem());
|
||||
}
|
||||
|
||||
public async Task MoveCursorDownPage()
|
||||
{
|
||||
await Tab.SelectNextItem(10);
|
||||
await RunFromCode(async () => await Tab.SelectNextItem(10));
|
||||
}
|
||||
|
||||
public async Task MoveCursorUp()
|
||||
{
|
||||
await Tab.SelectPreviousItem();
|
||||
await RunFromCode(async () => await Tab.SelectPreviousItem());
|
||||
}
|
||||
|
||||
public async Task MoveCursorUpPage()
|
||||
{
|
||||
await Tab.SelectPreviousItem(10);
|
||||
await RunFromCode(async () => await Tab.SelectPreviousItem(10));
|
||||
}
|
||||
|
||||
public async Task MoveCursorToFirst()
|
||||
{
|
||||
await Tab.SelectFirstItem();
|
||||
await RunFromCode(Tab.SelectFirstItem);
|
||||
}
|
||||
|
||||
public async Task MoveCursorToLast()
|
||||
{
|
||||
await Tab.SelectLastItem();
|
||||
await RunFromCode(Tab.SelectLastItem);
|
||||
}
|
||||
|
||||
public async Task GotToProvider()
|
||||
{
|
||||
await Tab.GoToProvider();
|
||||
await RunFromCode(Tab.GoToProvider);
|
||||
}
|
||||
|
||||
public async Task GotToRoot()
|
||||
{
|
||||
await Tab.GoToRoot();
|
||||
await RunFromCode(Tab.GoToRoot);
|
||||
}
|
||||
|
||||
public async Task GotToHome()
|
||||
@@ -300,28 +329,42 @@ namespace FileTime.Avalonia.Application
|
||||
var resolvedPath = await LocalContentProvider.GetByPath(path);
|
||||
if (resolvedPath is IContainer homeFolder)
|
||||
{
|
||||
await Tab.OpenContainer(homeFolder);
|
||||
await OpenContainer(homeFolder);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CreateContainer(string name)
|
||||
{
|
||||
(await Tab.GetCurrentLocation())?.CreateContainer(name);
|
||||
await RunFromCode(async () =>
|
||||
{
|
||||
var currentLocation = await Tab.GetCurrentLocation();
|
||||
if (currentLocation != null)
|
||||
{
|
||||
await currentLocation.CreateContainer(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task CreateElement(string name)
|
||||
{
|
||||
(await Tab.GetCurrentLocation())?.CreateElement(name);
|
||||
await RunFromCode(async () =>
|
||||
{
|
||||
var currentLocation = await Tab.GetCurrentLocation();
|
||||
if (currentLocation != null)
|
||||
{
|
||||
await currentLocation.CreateElement(name);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task OpenContainer(IContainer container)
|
||||
{
|
||||
await Tab.OpenContainer(container);
|
||||
await RunFromCode(async () => await Tab.OpenContainer(container));
|
||||
}
|
||||
|
||||
public async Task MarkCurrentItem()
|
||||
{
|
||||
await _tabState.MakrCurrentItem();
|
||||
await _tabState.MarkCurrentItem();
|
||||
}
|
||||
|
||||
public async Task UpdateMarkedItems(ContainerViewModel containerViewModel, CancellationToken token = default)
|
||||
|
||||
@@ -42,11 +42,16 @@ namespace FileTime.Avalonia.Converters
|
||||
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble != parameterDouble;
|
||||
return value != parameter;
|
||||
}
|
||||
if (ComparisonCondition == ComparisonCondition.Equal)
|
||||
else if (ComparisonCondition == ComparisonCondition.Equal)
|
||||
{
|
||||
if (value is int valueInt && (parameter is int parameterInt || int.TryParse(parameter?.ToString(), out parameterInt))) return valueInt == parameterInt;
|
||||
else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble == parameterDouble;
|
||||
else if (value?.GetType().IsEnum ?? false && Enum.TryParse(value.GetType(), parameter?.ToString(), out var _)) return value.ToString() == parameter?.ToString();
|
||||
else if (value?.GetType().IsEnum ?? false && Enum.TryParse(value.GetType(), parameter?.ToString(), out var _))
|
||||
{
|
||||
var s1 = value.ToString();
|
||||
var s2 = parameter?.ToString();
|
||||
return s1 == s2;
|
||||
}
|
||||
}
|
||||
|
||||
return value == parameter;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class IsElementConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) => value is IElement;
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,20 @@
|
||||
using FileTime.Core.Interactions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FileTime.Avalonia.Misc
|
||||
{
|
||||
public class InputElementWrapper
|
||||
{
|
||||
|
||||
public InputElement InputElement { get; }
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public char? PasswordChar { get; set; }
|
||||
|
||||
public InputElementWrapper(InputElement inputElement, string? defaultValue = null)
|
||||
{
|
||||
InputElement = inputElement;
|
||||
Value = defaultValue ?? "";
|
||||
PasswordChar = inputElement.InputType == InputType.Password ? '*' : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace FileTime.Avalonia.Models
|
||||
{
|
||||
public enum ElementPreviewMode
|
||||
{
|
||||
Unknown,
|
||||
Text,
|
||||
Empty
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,12 @@ namespace FileTime.Avalonia
|
||||
{
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
#if DEBUG
|
||||
catch
|
||||
{
|
||||
throw;
|
||||
}
|
||||
#else
|
||||
catch (Exception e)
|
||||
{
|
||||
var message = $"Ciritcal error cought in {nameof(Program)}";
|
||||
@@ -79,6 +85,7 @@ namespace FileTime.Avalonia
|
||||
using var streamWriter = new StreamWriter(fileWriter);
|
||||
streamWriter.WriteLine(DateTime.Now.ToString() + ": " + message + "\n" + e.ToString() + "\n\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
|
||||
@@ -133,12 +133,13 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
public async Task Init(bool initializeChildren = true, CancellationToken token = default)
|
||||
{
|
||||
await Refresh(initializeChildren, token);
|
||||
if (_isInitialized) return;
|
||||
await Refresh(initializeChildren, token: token);
|
||||
}
|
||||
|
||||
private async Task Container_Refreshed(object? sender, AsyncEventArgs e, CancellationToken token = default)
|
||||
{
|
||||
await Refresh(false, token);
|
||||
await Refresh(false, false, token);
|
||||
}
|
||||
|
||||
[Obsolete($"Use the parametrizable version of {nameof(Refresh)}.")]
|
||||
@@ -146,7 +147,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
await Refresh(true);
|
||||
}
|
||||
private async Task Refresh(bool initializeChildren, CancellationToken token = default)
|
||||
private async Task Refresh(bool initializeChildren, bool alloweReuse = true, CancellationToken token = default)
|
||||
{
|
||||
if (_isRefreshing) return;
|
||||
|
||||
@@ -157,18 +158,13 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
_isRefreshing = true;
|
||||
|
||||
var containers = (await _container.GetContainers())!.Select(c => AdoptOrReuseOrCreateItem(c, (c2) => new ContainerViewModel(_newItemProcessor, this, c2, ItemNameConverterService))).ToList();
|
||||
var elements = (await _container.GetElements())!.Select(e => AdoptOrReuseOrCreateItem(e, (e2) => new ElementViewModel(e2, this, ItemNameConverterService))).ToList();
|
||||
var containers = (await _container.GetContainers())!.Select(c => AdoptOrReuseOrCreateItem(c, alloweReuse , (c2) => new ContainerViewModel(_newItemProcessor, this, c2, ItemNameConverterService))).ToList();
|
||||
var elements = (await _container.GetElements())!.Select(e => AdoptOrReuseOrCreateItem(e, alloweReuse , (e2) => new ElementViewModel(e2, this, ItemNameConverterService))).ToList();
|
||||
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
Exceptions = new List<Exception>(_container.Exceptions);
|
||||
|
||||
foreach (var containerToRemove in _containers.Except(containers))
|
||||
{
|
||||
containerToRemove?.Dispose();
|
||||
}
|
||||
|
||||
if (initializeChildren)
|
||||
{
|
||||
foreach (var container in containers)
|
||||
@@ -178,14 +174,53 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _items.Count; i++)
|
||||
/*var containersToAdd = containers.Except(_containers).ToList();
|
||||
var containersToRemove = _containers.Except(containers).ToList();
|
||||
|
||||
var elementsToAdd = elements.Except(_elements).ToList();
|
||||
var elementsToRemove = _elements.Except(elements).ToList();
|
||||
|
||||
foreach (var containerToRemove in containersToRemove)
|
||||
{
|
||||
_items[i].IsAlternative = i % 2 == 1;
|
||||
Containers.Remove(containerToRemove);
|
||||
Items.Remove(containerToRemove);
|
||||
containerToRemove?.Dispose();
|
||||
}
|
||||
|
||||
foreach (var elementToRemove in elementsToRemove)
|
||||
{
|
||||
Elements.Remove(elementToRemove);
|
||||
Items.Remove(elementToRemove);
|
||||
}
|
||||
|
||||
foreach (var containerToAdd in containersToAdd)
|
||||
{
|
||||
Containers.Insert(GetNewItemPosition(containerToAdd, Containers), containerToAdd);
|
||||
Items.Insert(GetNewItemPosition(containerToAdd, Items), containerToAdd);
|
||||
}
|
||||
|
||||
foreach (var elementToAdd in elementsToAdd)
|
||||
{
|
||||
Elements.Insert(GetNewItemPosition(elementToAdd, Elements), elementToAdd);
|
||||
Items.Insert(GetNewItemPosition(elementToAdd, Items), elementToAdd);
|
||||
}*/
|
||||
|
||||
|
||||
|
||||
var containersToRemove = _containers.Except(containers).ToList();
|
||||
foreach (var containerToRemove in containersToRemove)
|
||||
{
|
||||
containerToRemove?.Dispose();
|
||||
}
|
||||
|
||||
Containers = new ObservableCollection<ContainerViewModel>(containers);
|
||||
Elements = new ObservableCollection<ElementViewModel>(elements);
|
||||
Items = new ObservableCollection<IItemViewModel>(containers.Cast<IItemViewModel>().Concat(elements));
|
||||
|
||||
for (var i = 0; i < Items.Count; i++)
|
||||
{
|
||||
Items[i].IsAlternative = i % 2 == 1;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -197,13 +232,31 @@ namespace FileTime.Avalonia.ViewModels
|
||||
_isRefreshing = false;
|
||||
}
|
||||
|
||||
private TResult AdoptOrReuseOrCreateItem<T, TResult>(T item, Func<T, TResult> generator) where T : class, IItem
|
||||
private int GetNewItemPosition<TItem, T>(TItem itemToAdd, IList<T> items) where TItem : IItemViewModel where T : IItemViewModel
|
||||
{
|
||||
var i = 0;
|
||||
for (; i < items.Count; i++)
|
||||
{
|
||||
var item = items[i];
|
||||
if (item is TItem && itemToAdd.Item.Name.CompareTo(item.Item.Name) < 0)
|
||||
{
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
private TResult AdoptOrReuseOrCreateItem<T, TResult>(T item, bool allowResuse, Func<T, TResult> generator) where T : class, IItem
|
||||
{
|
||||
var itemToAdopt = ChildrenToAdopt.Find(i => i.Item == item);
|
||||
if (itemToAdopt is TResult itemViewModel) return itemViewModel;
|
||||
|
||||
if (allowResuse)
|
||||
{
|
||||
var existingViewModel = _items?.FirstOrDefault(i => i.Item == item);
|
||||
if (existingViewModel is TResult itemViewModelToReuse) return itemViewModelToReuse;
|
||||
}
|
||||
|
||||
return generator(item);
|
||||
}
|
||||
@@ -221,26 +274,26 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
_containers = new ObservableCollection<ContainerViewModel>();
|
||||
_elements = new ObservableCollection<ElementViewModel>();
|
||||
_items = new ObservableCollection<IItemViewModel>();
|
||||
_containers.Clear();
|
||||
_elements.Clear();
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<ContainerViewModel>> GetContainers(CancellationToken token = default)
|
||||
{
|
||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token), token);
|
||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
||||
return _containers;
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<ElementViewModel>> GetElements(CancellationToken token = default)
|
||||
{
|
||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token), token);
|
||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
||||
return _elements;
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<IItemViewModel>> GetItems(CancellationToken token = default)
|
||||
{
|
||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token), token);
|
||||
if (!_isInitialized) await Task.Run(async () => await Refresh(false, token: token), token);
|
||||
return _items;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Core.Models;
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
[ViewModel]
|
||||
public partial class ElementPreviewViewModel
|
||||
{
|
||||
private const int MAXTEXTPREVIEWSIZE = 1024 * 1024;
|
||||
[Property]
|
||||
private IElement? _element;
|
||||
|
||||
[Property]
|
||||
private string? _textContent;
|
||||
|
||||
[Property]
|
||||
private ElementPreviewMode? _mode;
|
||||
|
||||
public async Task Init(IElement element, CancellationToken token = default)
|
||||
{
|
||||
Element = element;
|
||||
|
||||
var elementSize = await element.GetElementSize(token);
|
||||
if (elementSize == 0)
|
||||
{
|
||||
Mode = ElementPreviewMode.Empty;
|
||||
}
|
||||
else if (elementSize < MAXTEXTPREVIEWSIZE)
|
||||
{
|
||||
TextContent = await element.GetContent();
|
||||
Mode = ElementPreviewMode.Text;
|
||||
}
|
||||
else
|
||||
{
|
||||
Mode = ElementPreviewMode.Unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,10 +74,10 @@ namespace FileTime.Avalonia.ViewModels
|
||||
private string _messageBoxText;
|
||||
|
||||
[Property]
|
||||
private ObservableCollection<string> _popupTexts = new ObservableCollection<string>();
|
||||
private ObservableCollection<string> _popupTexts = new();
|
||||
|
||||
[Property]
|
||||
private bool _showAllShortcut;
|
||||
private bool _isAllShortcutVisible;
|
||||
|
||||
[Property]
|
||||
private List<CommandBinding> _allShortcut;
|
||||
@@ -127,7 +127,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
var driveInfos = new List<RootDriveInfo>();
|
||||
var drives = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
|
||||
? DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed)
|
||||
: DriveInfo.GetDrives().Where(d =>
|
||||
: DriveInfo.GetDrives().Where(d =>
|
||||
d.DriveType == DriveType.Fixed
|
||||
&& d.DriveFormat != "pstorefs"
|
||||
&& d.DriveFormat != "bpf_fs"
|
||||
@@ -716,9 +716,58 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
private Task ShowAllShortcut2()
|
||||
private Task ShowAllShortcut()
|
||||
{
|
||||
ShowAllShortcut = true;
|
||||
IsAllShortcutVisible = true;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task RunCommandInContainer()
|
||||
{
|
||||
var handler = () =>
|
||||
{
|
||||
if (Inputs != null)
|
||||
{
|
||||
var input = Inputs[0].Value;
|
||||
string? path = null;
|
||||
string? arguments = null;
|
||||
|
||||
if (input.StartsWith("\""))
|
||||
{
|
||||
var pathEnd = input.IndexOf('\"', 1);
|
||||
|
||||
path = input.Substring(1, pathEnd);
|
||||
arguments = input.Substring(pathEnd + 1).Trim();
|
||||
}
|
||||
else
|
||||
{
|
||||
var inputParts = input.Split(' ');
|
||||
path = inputParts[0];
|
||||
arguments = inputParts.Length > 1 ? string.Join(' ', inputParts[1..]).Trim() : null;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(path))
|
||||
{
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = path;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(arguments))
|
||||
{
|
||||
process.StartInfo.Arguments = arguments;
|
||||
}
|
||||
if (AppState.SelectedTab.CurrentLocation.Container is LocalFolder localFolder)
|
||||
{
|
||||
process.StartInfo.WorkingDirectory = localFolder.Directory.FullName;
|
||||
}
|
||||
process.Start();
|
||||
}
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
|
||||
ReadInputs(new List<Core.Interactions.InputElement>() { new Core.Interactions.InputElement("Command", InputType.Text) }, handler);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -757,14 +806,14 @@ namespace FileTime.Avalonia.ViewModels
|
||||
_inputHandler = null;
|
||||
}
|
||||
|
||||
public async Task<bool> ProcessKeyDown(Key key, KeyModifiers keyModifiers)
|
||||
public async void ProcessKeyDown(Key key, KeyModifiers keyModifiers, Action<bool> setHandled)
|
||||
{
|
||||
if (key == Key.LeftAlt
|
||||
|| key == Key.RightAlt
|
||||
|| key == Key.LeftShift
|
||||
|| key == Key.RightShift
|
||||
|| key == Key.LeftCtrl
|
||||
|| key == Key.RightCtrl) return false;
|
||||
|| key == Key.RightCtrl) return;
|
||||
|
||||
NoCommandFound = false;
|
||||
|
||||
@@ -782,12 +831,14 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
if (key == Key.Escape)
|
||||
{
|
||||
ShowAllShortcut = false;
|
||||
IsAllShortcutVisible = false;
|
||||
_previousKeys.Clear();
|
||||
PossibleCommands = new();
|
||||
setHandled(true);
|
||||
}
|
||||
else if (selectedCommandBinding != null)
|
||||
{
|
||||
setHandled(true);
|
||||
await selectedCommandBinding.InvokeAsync();
|
||||
_previousKeys.Clear();
|
||||
PossibleCommands = new();
|
||||
@@ -796,10 +847,11 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
_previousKeys.Clear();
|
||||
PossibleCommands = new();
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
else if (_previousKeys.Count == 2)
|
||||
{
|
||||
setHandled(true);
|
||||
NoCommandFound = true;
|
||||
_previousKeys.Clear();
|
||||
PossibleCommands = new();
|
||||
@@ -817,6 +869,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
PossibleCommands = possibleCommands;
|
||||
}
|
||||
setHandled(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -826,9 +879,10 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
if (key == Key.Escape)
|
||||
{
|
||||
if (ShowAllShortcut)
|
||||
setHandled(true);
|
||||
if (IsAllShortcutVisible)
|
||||
{
|
||||
ShowAllShortcut = false;
|
||||
IsAllShortcutVisible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -839,12 +893,14 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
if (AppState.RapidTravelText.Length > 0)
|
||||
{
|
||||
setHandled(true);
|
||||
AppState.RapidTravelText = AppState.RapidTravelText.Substring(0, AppState.RapidTravelText.Length - 1);
|
||||
updateRapidTravelFilter = true;
|
||||
}
|
||||
}
|
||||
else if (keyString.Length == 1)
|
||||
{
|
||||
setHandled(true);
|
||||
AppState.RapidTravelText += keyString.ToString().ToLower();
|
||||
updateRapidTravelFilter = true;
|
||||
}
|
||||
@@ -854,12 +910,8 @@ namespace FileTime.Avalonia.ViewModels
|
||||
var selectedCommandBinding = _universalCommandBindings.Find(c => AreKeysEqual(c.Keys, currentKeyAsList));
|
||||
if (selectedCommandBinding != null)
|
||||
{
|
||||
setHandled(true);
|
||||
await selectedCommandBinding.InvokeAsync();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -895,13 +947,6 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<bool> ProcessKeyUp(Key key, KeyModifiers keyModifiers)
|
||||
{
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
private void ReadInputs(List<Core.Interactions.InputElement> inputs, Action inputHandler)
|
||||
@@ -1144,7 +1189,12 @@ namespace FileTime.Avalonia.ViewModels
|
||||
"show all shortcut",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[] { new KeyWithModifiers(Key.F1) },
|
||||
ShowAllShortcut2),
|
||||
ShowAllShortcut),
|
||||
new CommandBinding(
|
||||
"run command",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[] { new KeyWithModifiers(Key.D4, shift: true) },
|
||||
RunCommandInContainer),
|
||||
//TODO REMOVE
|
||||
new CommandBinding(
|
||||
"open in default file browser",
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:FileTime.Avalonia.ViewModels"
|
||||
xmlns:local="using:FileTime.Avalonia.Views"
|
||||
xmlns:models="using:FileTime.Avalonia.Models"
|
||||
Title="FileTime"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
Icon="/Assets/filetime.ico"
|
||||
InputElement.KeyDown="OnKeyDown"
|
||||
InputElement.KeyUp="OnKeyUp"
|
||||
TransparencyLevelHint="Blur"
|
||||
Background="Transparent"
|
||||
ExtendClientAreaToDecorationsHint="True"
|
||||
@@ -100,7 +100,7 @@
|
||||
Margin="5,0,0,0"
|
||||
Grid.Column="1"
|
||||
Grid.ColumnSpan="2"
|
||||
Grid.Row="1"
|
||||
Grid.Row="1"
|
||||
MinWidth="100"
|
||||
HorizontalAlignment="Stretch"
|
||||
Maximum="100"
|
||||
@@ -269,123 +269,65 @@
|
||||
VerticalAlignment="Stretch"
|
||||
Fill="{DynamicResource ContentSeparatorBrush}" />
|
||||
|
||||
<Grid Grid.Column="4" IsVisible="{Binding AppState.SelectedTab.ChildContainer,Converter={StaticResource IsNotNullConverter}}">
|
||||
<ListBox
|
||||
Classes="ContentListView"
|
||||
IsEnabled="False"
|
||||
x:Name="ChildItems"
|
||||
Items="{Binding AppState.SelectedTab.ChildContainer.Items}"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:ItemView/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
<Grid Grid.Column="4">
|
||||
<Grid IsVisible="{Binding AppState.SelectedTab.ChildContainer,Converter={StaticResource IsNotNullConverter}}">
|
||||
<ListBox
|
||||
Classes="ContentListView"
|
||||
IsEnabled="False"
|
||||
x:Name="ChildItems"
|
||||
Items="{Binding AppState.SelectedTab.ChildContainer.Items}"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:ItemView/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<Grid
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||
|
||||
<TextBlock
|
||||
x:Name="ChildEmpty"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource ErrorBrush}"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||
Empty
|
||||
</TextBlock>
|
||||
|
||||
<Grid
|
||||
RowDefinitions="Auto, Auto"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||
<Grid IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||
|
||||
<TextBlock
|
||||
Margin="0,0,0,10"
|
||||
x:Name="ChildEmpty"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Text="There were some errors while opening container."
|
||||
Foreground="{DynamicResource ErrorBrush}" />
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource ErrorBrush}"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||
Empty
|
||||
</TextBlock>
|
||||
|
||||
<ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</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>
|
||||
<Grid
|
||||
RowDefinitions="Auto, Auto"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||
|
||||
<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}" />
|
||||
Margin="0,0,0,10"
|
||||
HorizontalAlignment="Center"
|
||||
TextWrapping="Wrap"
|
||||
Text="There were some errors while opening container."
|
||||
Foreground="{DynamicResource ErrorBrush}" />
|
||||
|
||||
<ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</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>
|
||||
</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 IsVisible="{Binding AppState.SelectedTab.ElementPreview, Converter={StaticResource IsNotNullConverter}}">
|
||||
<TextBlock HorizontalAlignment="Center" Text="Don't know how to preview this file" IsVisible="{Binding AppState.SelectedTab.ElementPreview.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static models:ElementPreviewMode.Unknown}}"/>
|
||||
<TextBlock HorizontalAlignment="Center" Text="Empty" IsVisible="{Binding AppState.SelectedTab.ElementPreview.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static models:ElementPreviewMode.Empty}}"/>
|
||||
<ScrollViewer IsVisible="{Binding AppState.SelectedTab.ElementPreview.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static models:ElementPreviewMode.Text}}">
|
||||
<TextBox
|
||||
IsReadOnly="True"
|
||||
Text="{Binding AppState.SelectedTab.ElementPreview.TextContent}" />
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<ItemsRepeater Items="{Binding PopupTexts}" Margin="0,0,0,20" HorizontalAlignment="Center" VerticalAlignment="Bottom" IsVisible="{Binding PopupTexts.Count,Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||
@@ -496,7 +438,99 @@
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Border Background="{DynamicResource TransparentContainerBackgroundBrush}" Margin="20" HorizontalAlignment="Center" IsVisible="{Binding ShowAllShortcut}">
|
||||
<Border
|
||||
Background="{DynamicResource BarelyTransparentBackgroundColor}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding Inputs, Converter={StaticResource IsNotNullConverter}}">
|
||||
<Border
|
||||
Background="{DynamicResource ContainerBackgroundBrush}"
|
||||
Padding="20"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center">
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
|
||||
<ItemsControl
|
||||
x:Name="InputList"
|
||||
Items="{Binding Inputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid MinWidth="500" ColumnDefinitions="250,*" Margin="10,5">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding InputElement.Text}" />
|
||||
<TextBox
|
||||
PasswordChar="{Binding PasswordChar}"
|
||||
AttachedToVisualTree="InputText_AttachedToVisualTree"
|
||||
IsTabStop="True"
|
||||
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"
|
||||
Margin="0,10,0,0">
|
||||
<Button
|
||||
HorizontalContentAlignment="Center"
|
||||
Width="80"
|
||||
Command="{Binding ProcessInputsCommand}"
|
||||
Content="Ok" />
|
||||
<Button
|
||||
HorizontalContentAlignment="Center"
|
||||
Width="80"
|
||||
Margin="10,0,0,0"
|
||||
Command="{Binding CancelInputsCommand}"
|
||||
Content="Cancel" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
Background="{DynamicResource BarelyTransparentBackgroundColor}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
IsVisible="{Binding MessageBoxText, Converter={StaticResource IsNotNullConverter}}">
|
||||
<Border
|
||||
Background="{DynamicResource ContainerBackgroundBrush}"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Padding="20">
|
||||
<Grid RowDefinitions="Auto,Auto">
|
||||
|
||||
<TextBlock Text="{Binding MessageBoxText}"/>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Orientation="Horizontal"
|
||||
Margin="0,10,0,0">
|
||||
<Button
|
||||
HorizontalContentAlignment="Center"
|
||||
Width="80"
|
||||
Command="{Binding ProcessMessageBoxCommand}"
|
||||
Content="Yes" />
|
||||
<Button
|
||||
HorizontalContentAlignment="Center"
|
||||
Width="80"
|
||||
Margin="10,0,0,0"
|
||||
Command="{Binding CancelMessageBoxCommand}"
|
||||
Content="No" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Border>
|
||||
|
||||
<Border
|
||||
Background="{DynamicResource TransparentContainerBackgroundBrush}"
|
||||
Margin="20"
|
||||
HorizontalAlignment="Center"
|
||||
IsVisible="{Binding IsAllShortcutVisible}">
|
||||
<Grid RowDefinitions="Auto, *" Margin="30,10">
|
||||
<TextBlock Text="Shortcuts" Margin="0,0,0,20"/>
|
||||
<ScrollViewer
|
||||
|
||||
@@ -45,19 +45,11 @@ namespace FileTime.Avalonia.Views
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public async void OnKeyDown(object sender, KeyEventArgs e)
|
||||
public void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (_inputElementWrapper == null)
|
||||
{
|
||||
e.Handled = e.Handled || await ViewModel!.ProcessKeyDown(e.Key, e.KeyModifiers);
|
||||
}
|
||||
}
|
||||
|
||||
public async void OnKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (_inputElementWrapper == null)
|
||||
{
|
||||
e.Handled = e.Handled || await ViewModel!.ProcessKeyUp(e.Key, e.KeyModifiers);
|
||||
ViewModel!.ProcessKeyDown(e.Key, e.KeyModifiers, h => e.Handled = h);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +79,7 @@ namespace FileTime.Avalonia.Views
|
||||
|
||||
private void InputText_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper)
|
||||
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper)
|
||||
{
|
||||
_inputElementWrapper = null;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ namespace FileTime.Providers.Local
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public bool IsDisposed => false;
|
||||
|
||||
public LocalContentProvider(ILogger<LocalContentProvider> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
@@ -100,5 +102,7 @@ namespace FileTime.Providers.Local
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
public Task<bool> CanOpen() => Task.FromResult(true);
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,8 @@ namespace FileTime.Providers.Local
|
||||
|
||||
private readonly LocalFolder _parent;
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public LocalFile(FileInfo file, LocalFolder parent, IContentProvider contentProvider)
|
||||
{
|
||||
_parent = parent;
|
||||
@@ -80,5 +82,10 @@ namespace FileTime.Providers.Local
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
|
||||
public async Task<string> GetContent(CancellationToken token = default) => await System.IO.File.ReadAllTextAsync(File.FullName, token);
|
||||
public Task<long> GetElementSize(CancellationToken token = default) => Task.FromResult(File.Length);
|
||||
|
||||
public void Dispose() => IsDisposed = true;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ namespace FileTime.Providers.Local
|
||||
private IReadOnlyList<IItem>? _items;
|
||||
private IReadOnlyList<IContainer>? _containers;
|
||||
private IReadOnlyList<IElement>? _elements;
|
||||
private List<Exception> _exceptions;
|
||||
private readonly List<Exception> _exceptions;
|
||||
private readonly IContainer? _parent;
|
||||
|
||||
public bool IsHidden => (Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||
@@ -34,6 +34,8 @@ namespace FileTime.Providers.Local
|
||||
public DateTime CreatedAt => Directory.CreationTime;
|
||||
public IReadOnlyList<Exception> Exceptions { get; }
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete { get; }
|
||||
|
||||
public LocalFolder(DirectoryInfo directory, LocalContentProvider contentProvider, IContainer? parent)
|
||||
@@ -59,8 +61,16 @@ namespace FileTime.Providers.Local
|
||||
|
||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)new LocalFolder(Directory, Provider, _parent));
|
||||
|
||||
public Task RefreshAsync(CancellationToken token = default)
|
||||
public async Task RefreshAsync(CancellationToken token = default)
|
||||
{
|
||||
if (_items != null)
|
||||
{
|
||||
foreach (var item in _items)
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_containers = new List<IContainer>();
|
||||
_elements = new List<IElement>();
|
||||
_items = new List<IItem>();
|
||||
@@ -68,13 +78,13 @@ namespace FileTime.Providers.Local
|
||||
|
||||
try
|
||||
{
|
||||
if (token.IsCancellationRequested) return Task.CompletedTask;
|
||||
if (token.IsCancellationRequested) return;
|
||||
_containers = Directory.GetDirectories().Select(d => new LocalFolder(d, Provider, this)).OrderBy(d => d.Name).ToList().AsReadOnly();
|
||||
|
||||
if (token.IsCancellationRequested) return Task.CompletedTask;
|
||||
if (token.IsCancellationRequested) return;
|
||||
_elements = Directory.GetFiles().Select(f => new LocalFile(f, this, Provider)).OrderBy(f => f.Name).ToList().AsReadOnly();
|
||||
|
||||
if (token.IsCancellationRequested) return Task.CompletedTask;
|
||||
if (token.IsCancellationRequested) return;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -82,9 +92,7 @@ namespace FileTime.Providers.Local
|
||||
}
|
||||
|
||||
_items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly();
|
||||
Refreshed?.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
|
||||
return Task.CompletedTask;
|
||||
if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default)
|
||||
@@ -176,5 +184,13 @@ namespace FileTime.Providers.Local
|
||||
}
|
||||
}
|
||||
public Task<bool> CanOpen() => Task.FromResult(true);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_items = null;
|
||||
_containers = null;
|
||||
_elements = null;
|
||||
IsDisposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,8 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public bool IsDisposed => false;
|
||||
|
||||
public SmbContentProvider(IInputInterface inputInterface)
|
||||
{
|
||||
_rootContainers = new List<IContainer>();
|
||||
@@ -102,5 +104,7 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
public Task<bool> CanOpen() => Task.FromResult(true);
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ namespace FileTime.Providers.Smb
|
||||
public IContentProvider Provider { get; }
|
||||
private IContainer _parent;
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public SmbFile(string name, SmbContentProvider provider, IContainer parent)
|
||||
{
|
||||
Name = name;
|
||||
@@ -43,5 +45,9 @@ namespace FileTime.Providers.Smb
|
||||
}
|
||||
|
||||
public IContainer? GetParent() => _parent;
|
||||
public Task<string> GetContent(CancellationToken token = default) => Task.FromResult("NotImplemented");
|
||||
public Task<long> GetElementSize(CancellationToken token = default) => Task.FromResult(-1L);
|
||||
|
||||
public void Dispose() => IsDisposed = true;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ namespace FileTime.Providers.Smb
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent)
|
||||
@@ -103,6 +105,14 @@ namespace FileTime.Providers.Smb
|
||||
_containers = containers.AsReadOnly();
|
||||
_elements = elements.AsReadOnly();
|
||||
|
||||
if (_items != null)
|
||||
{
|
||||
foreach (var item in _items)
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly();
|
||||
await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
@@ -123,5 +133,7 @@ namespace FileTime.Providers.Smb
|
||||
return _elements;
|
||||
}
|
||||
public Task<bool> CanOpen() => Task.FromResult(true);
|
||||
|
||||
public void Dispose() => IsDisposed = true;
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public bool IsDisposed => false;
|
||||
|
||||
public SmbServer(string path, SmbContentProvider contentProvider, IInputInterface inputInterface)
|
||||
{
|
||||
_inputInterface = inputInterface;
|
||||
@@ -97,7 +99,7 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
_shares = shares.ConvertAll(s => new SmbShare(s, Provider, this, _smbClientContext)).AsReadOnly();
|
||||
_items = _shares.Cast<IItem>().ToList().AsReadOnly();
|
||||
await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty);
|
||||
await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
|
||||
}
|
||||
|
||||
public Task<IContainer> Clone() => Task.FromResult((IContainer)this);
|
||||
@@ -190,5 +192,7 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
public Task<bool> CanOpen() => Task.FromResult(true);
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ namespace FileTime.Providers.Smb
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
|
||||
|
||||
public bool IsDisposed => false;
|
||||
|
||||
public bool SupportsDirectoryLevelSoftDelete => false;
|
||||
|
||||
public SmbShare(string name, SmbContentProvider contentProvider, IContainer parent, SmbClientContext smbClientContext)
|
||||
@@ -111,6 +113,14 @@ namespace FileTime.Providers.Smb
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (_items != null)
|
||||
{
|
||||
foreach (var item in _items)
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_containers = containers.AsReadOnly();
|
||||
_elements = elements.AsReadOnly();
|
||||
|
||||
@@ -160,5 +170,7 @@ namespace FileTime.Providers.Smb
|
||||
|
||||
public Task Rename(string newName) => throw new NotSupportedException();
|
||||
public Task<bool> CanOpen() => Task.FromResult(true);
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user