Optimization, UI improvements, Run command

This commit is contained in:
2022-02-05 00:04:27 +01:00
parent bca940372a
commit c061f658aa
31 changed files with 579 additions and 212 deletions

View File

@@ -101,7 +101,7 @@ namespace FileTime.App.Core.Tab
: new List<AbsolutePath>().AsReadOnly(); : new List<AbsolutePath>().AsReadOnly();
} }
public async Task MakrCurrentItem() public async Task MarkCurrentItem()
{ {
var currentLocation = await Tab!.GetCurrentLocation(); var currentLocation = await Tab!.GetCurrentLocation();
if (currentLocation != null) if (currentLocation != null)

View File

@@ -67,7 +67,7 @@ namespace FileTime.ConsoleUI.App
{ {
if (_selectedTab != null) if (_selectedTab != null)
{ {
await _tabStates[_selectedTab].MakrCurrentItem(); await _tabStates[_selectedTab].MarkCurrentItem();
} }
} }

View File

@@ -27,7 +27,7 @@ namespace FileTime.Core.Command
if (itemToRename != null) if (itemToRename != null)
{ {
await itemToRename.Rename(Target); 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()!));
} }
} }

View File

@@ -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. #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
private string? _lastPath; private string? _lastPath;
private readonly object _guardSetCurrentSelectedItemCTS = new object(); private bool _currentlySelecting = false;
private readonly object _guardSetCurrentSelectedItemCTS = new();
private CancellationTokenSource? _setCurrentSelectedItemCTS; private CancellationTokenSource? _setCurrentSelectedItemCTS;
public int CurrentSelectedIndex { get; private set; } public int CurrentSelectedIndex { get; private set; }
@@ -54,6 +56,8 @@ namespace FileTime.Core.Components
public async Task SetCurrentSelectedItem(IItem? value, bool secondary = false) public async Task SetCurrentSelectedItem(IItem? value, bool secondary = false)
{ {
if (_currentlySelecting) return;
if (_currentSelectedItem != value) if (_currentSelectedItem != value)
{ {
IItem? itemToSelect = null; IItem? itemToSelect = null;
@@ -190,11 +194,13 @@ namespace FileTime.Core.Components
{ {
if (!currentPossibleItems.Any()) return; if (!currentPossibleItems.Any()) return;
var currentLocationItems = (await (await GetCurrentLocation()).GetItems())!; var currentLocation = await GetCurrentLocation();
var currentLocationItems = (await currentLocation.GetItems())!;
if (await GetCurrentSelectedItem() != null) if (await GetCurrentSelectedItem() != null)
{ {
(await GetCurrentLocation())?.RefreshAsync(); _currentlySelecting = true;
currentLocation?.RefreshAsync();
IItem? newSelectedItem = null; IItem? newSelectedItem = null;
foreach (var item in currentPossibleItems) foreach (var item in currentPossibleItems)
@@ -212,6 +218,7 @@ namespace FileTime.Core.Components
newSelectedItem = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == newSelectedItem.Name); newSelectedItem = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == newSelectedItem.Name);
} }
_currentlySelecting = false;
await SetCurrentSelectedItem(newSelectedItem ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null)); await SetCurrentSelectedItem(newSelectedItem ?? (currentLocationItems.Count > 0 ? currentLocationItems[0] : null));
} }
else else
@@ -261,6 +268,11 @@ namespace FileTime.Core.Components
var newCurrentLocation = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == lastCurrentLocation.Name); var newCurrentLocation = (await (await GetCurrentLocation()).GetItems())?.FirstOrDefault(i => i.Name == lastCurrentLocation.Name);
await SetCurrentSelectedItem(newCurrentLocation); await SetCurrentSelectedItem(newCurrentLocation);
} }
foreach(var lastLocationItem in currentLocationItems.OfType<IContainer>())
{
lastLocationItem.Dispose();
}
} }
} }

View File

@@ -4,5 +4,7 @@ namespace FileTime.Core.Models
{ {
bool IsSpecial { get; } bool IsSpecial { get; }
string GetPrimaryAttributeText(); string GetPrimaryAttributeText();
Task<string> GetContent(CancellationToken token = default);
Task<long> GetElementSize(CancellationToken token = default);
} }
} }

View File

@@ -2,11 +2,12 @@ using FileTime.Core.Providers;
namespace FileTime.Core.Models namespace FileTime.Core.Models
{ {
public interface IItem public interface IItem : IDisposable
{ {
string Name { get; } string Name { get; }
string? FullName { get; } string? FullName { get; }
bool IsHidden { get; } bool IsHidden { get; }
bool IsDisposed { get; }
SupportsDelete CanDelete { get; } SupportsDelete CanDelete { get; }
bool CanRename { get; } bool CanRename { get; }
IContentProvider Provider { get; } IContentProvider Provider { get; }

View File

@@ -35,6 +35,8 @@ namespace FileTime.Core.Models
public AsyncEventHandler Refreshed { get; } public AsyncEventHandler Refreshed { get; }
public bool IsDisposed => BaseContainer.IsDisposed;
private void RefreshAddBase(Func<object?, AsyncEventArgs, CancellationToken, Task> handler) private void RefreshAddBase(Func<object?, AsyncEventArgs, CancellationToken, Task> handler)
{ {
BaseContainer.Refreshed.Add(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 Rename(string newName) => await BaseContainer.Rename(newName);
public async Task<bool> CanOpen() => await BaseContainer.CanOpen(); public async Task<bool> CanOpen() => await BaseContainer.CanOpen();
public void Dispose()
{
BaseContainer.Dispose();
}
} }
} }

View File

@@ -33,6 +33,8 @@ namespace FileTime.Core.Providers
public bool SupportsDirectoryLevelSoftDelete => false; public bool SupportsDirectoryLevelSoftDelete => false;
public bool IsDisposed => false;
public TopContainer(IEnumerable<IContentProvider> contentProviders) public TopContainer(IEnumerable<IContentProvider> contentProviders)
{ {
_contentProviders = new List<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 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<IItem>?> GetItems(CancellationToken token = default) => Task.FromResult(_items);
public Task<IReadOnlyList<IContainer>?> GetContainers(CancellationToken token = default) => Task.FromResult(_containers); 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 Rename(string newName) => throw new NotSupportedException();
public Task<bool> CanOpen() => Task.FromResult(true); public Task<bool> CanOpen() => Task.FromResult(true);
public void Dispose() { }
} }
} }

View File

@@ -29,6 +29,8 @@ namespace FileTime.Core.Timeline
public bool SupportsDirectoryLevelSoftDelete => false; public bool SupportsDirectoryLevelSoftDelete => false;
public bool IsDisposed { get; private set; }
public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime) public TimeContainer(string name, IContainer parent, IContentProvider contentProvider, IContentProvider virtualContentProvider, PointInTime pointInTime)
{ {
_parent = parent; _parent = parent;
@@ -121,5 +123,7 @@ namespace FileTime.Core.Timeline
return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider); return new TimeElement(elementDiff.Name, this, Provider, elementDiff.AbsolutePath.VirtualContentProvider ?? elementDiff.AbsolutePath.ContentProvider);
} }
public Task<bool> CanOpen() => Task.FromResult(true); public Task<bool> CanOpen() => Task.FromResult(true);
public void Dispose() => IsDisposed = true;
} }
} }

View File

@@ -31,6 +31,8 @@ namespace FileTime.Core.Timeline
public IContentProvider Provider { get; } public IContentProvider Provider { get; }
public IContentProvider VirtualProvider { get; } public IContentProvider VirtualProvider { get; }
public bool IsDisposed { get; private set; }
public Task Delete(bool hardDelete = false) => Task.CompletedTask; public Task Delete(bool hardDelete = false) => Task.CompletedTask;
public IContainer? GetParent() => _parent; public IContainer? GetParent() => _parent;
@@ -38,5 +40,10 @@ namespace FileTime.Core.Timeline
public string GetPrimaryAttributeText() => ""; public string GetPrimaryAttributeText() => "";
public Task Rename(string newName) => Task.CompletedTask; 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;
} }
} }

View File

@@ -28,6 +28,8 @@ namespace FileTime.Core.Timeline
public bool SupportsDirectoryLevelSoftDelete => false; public bool SupportsDirectoryLevelSoftDelete => false;
public bool IsDisposed => false;
public TimeProvider(PointInTime pointInTime) public TimeProvider(PointInTime pointInTime)
{ {
_pointInTime = pointInTime; _pointInTime = pointInTime;
@@ -90,5 +92,7 @@ namespace FileTime.Core.Timeline
public void SetParent(IContainer container) { } public void SetParent(IContainer container) { }
public Task<bool> CanOpen() => Task.FromResult(true); public Task<bool> CanOpen() => Task.FromResult(true);
public void Dispose() { }
} }
} }

View File

@@ -12,6 +12,7 @@
<Color x:Key="AppBackgroundColor">#E7073642</Color> <Color x:Key="AppBackgroundColor">#E7073642</Color>
<Color x:Key="ContainerBackgroundColor">#083e4c</Color> <Color x:Key="ContainerBackgroundColor">#083e4c</Color>
<Color x:Key="TransparentContainerBackgroundColor">#D0083e4c</Color> <Color x:Key="TransparentContainerBackgroundColor">#D0083e4c</Color>
<Color x:Key="BarelyTransparentBackgroundColor">#80083e4c</Color>
<Color x:Key="ItemBackgroundColor">#00000000</Color> <Color x:Key="ItemBackgroundColor">#00000000</Color>
<Color x:Key="AlternativeItemBackgroundColor">#10000000</Color> <Color x:Key="AlternativeItemBackgroundColor">#10000000</Color>
@@ -128,6 +129,7 @@
<converters:IsEmptyConverter x:Key="IsNotEmptyConverter" Inverse="true"/> <converters:IsEmptyConverter x:Key="IsNotEmptyConverter" Inverse="true"/>
<converters:ExceptionToStringConverter x:Key="ExceptionToStringConverter"/> <converters:ExceptionToStringConverter x:Key="ExceptionToStringConverter"/>
<converters:BoolInverter x:Key="BoolInverter"/> <converters:BoolInverter x:Key="BoolInverter"/>
<converters:IsElementConverter x:Key="IsElementConverter"/>
</ResourceDictionary> </ResourceDictionary>
</Application.Resources> </Application.Resources>
@@ -145,6 +147,10 @@
<Style Selector="TextBlock.ExtraSmallText"> <Style Selector="TextBlock.ExtraSmallText">
<Setter Property="FontSize" Value="11"/> <Setter Property="FontSize" Value="11"/>
</Style> </Style>
<Style Selector="TextBox">
<Setter Property="Foreground" Value="{DynamicResource ForegroundBrush}"/>
<Setter Property="Background" Value="{DynamicResource ContainerBackgroundBrush}"/>
</Style>
<Style Selector="ListBox.ContentListView"> <Style Selector="ListBox.ContentListView">
<Setter Property="Background" Value="Transparent"/> <Setter Property="Background" Value="Transparent"/>
</Style> </Style>

View File

@@ -20,6 +20,7 @@ namespace FileTime.Avalonia.Application
[Inject(typeof(Tab))] [Inject(typeof(Tab))]
public partial class TabContainer : INewItemProcessor public partial class TabContainer : INewItemProcessor
{ {
private bool _updateFromCode;
[Property] [Property]
private TabState _tabState; private TabState _tabState;
@@ -46,11 +47,13 @@ namespace FileTime.Avalonia.Application
get => _selectedItem; get => _selectedItem;
set set
{ {
if (value != null) if (!_updateFromCode && value != null)
{ {
try try
{ {
SetSelectedItemAsync(value, true).Wait(); /*var task = SetSelectedItemAsync(value, true);
Task.WaitAll(new Task[] { task }, 100);*/
SetSelectedItemAsync(value, true);
} }
catch catch
{ {
@@ -60,14 +63,28 @@ namespace FileTime.Avalonia.Application
} }
} }
[Property]
private ElementPreviewViewModel? _elementPreview;
public async Task SetSelectedItemAsync(IItemViewModel? value, bool fromDataBinding = false) public async Task SetSelectedItemAsync(IItemViewModel? value, bool fromDataBinding = false)
{ {
if (_selectedItem != value) if (_selectedItem != value)
{ {
_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); await Tab.SetCurrentSelectedItem(SelectedItem?.Item, fromDataBinding);
OnPropertyChanged(nameof(SelectedItem));
} }
} }
@@ -174,7 +191,7 @@ namespace FileTime.Avalonia.Application
if (token.IsCancellationRequested) return; if (token.IsCancellationRequested) return;
var items = await _currentLocation.GetItems(); var items = await _currentLocation.GetItems(token);
if (items?.Count > 0) if (items?.Count > 0)
{ {
foreach (var item in items) foreach (var item in items)
@@ -198,7 +215,7 @@ namespace FileTime.Avalonia.Application
{ {
if (token.IsCancellationRequested) return; if (token.IsCancellationRequested) return;
var activeChildItem = await Tab.GetItemByLastPath(containerViewModel.Container); 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) if (child != null)
{ {
child.IsSelected = true; child.IsSelected = true;
@@ -239,59 +256,71 @@ namespace FileTime.Avalonia.Application
catch { } catch { }
} }
private async Task RunFromCode(Func<Task> task)
{
_updateFromCode = true;
try
{
await task();
}
catch
{
_updateFromCode = false;
throw;
}
}
public async Task Open() public async Task Open()
{ {
if (ChildContainer != null) if (ChildContainer != null)
{ {
await Tab.Open(); await RunFromCode(Tab.Open);
await UpdateCurrentSelectedItem();
} }
} }
public async Task GoUp() public async Task GoUp()
{ {
await Tab.GoUp(); await RunFromCode(Tab.GoUp);
await UpdateCurrentSelectedItem();
} }
public async Task MoveCursorDown() public async Task MoveCursorDown()
{ {
await Tab.SelectNextItem(); await RunFromCode(async () => await Tab.SelectNextItem());
} }
public async Task MoveCursorDownPage() public async Task MoveCursorDownPage()
{ {
await Tab.SelectNextItem(10); await RunFromCode(async () => await Tab.SelectNextItem(10));
} }
public async Task MoveCursorUp() public async Task MoveCursorUp()
{ {
await Tab.SelectPreviousItem(); await RunFromCode(async () => await Tab.SelectPreviousItem());
} }
public async Task MoveCursorUpPage() public async Task MoveCursorUpPage()
{ {
await Tab.SelectPreviousItem(10); await RunFromCode(async () => await Tab.SelectPreviousItem(10));
} }
public async Task MoveCursorToFirst() public async Task MoveCursorToFirst()
{ {
await Tab.SelectFirstItem(); await RunFromCode(Tab.SelectFirstItem);
} }
public async Task MoveCursorToLast() public async Task MoveCursorToLast()
{ {
await Tab.SelectLastItem(); await RunFromCode(Tab.SelectLastItem);
} }
public async Task GotToProvider() public async Task GotToProvider()
{ {
await Tab.GoToProvider(); await RunFromCode(Tab.GoToProvider);
} }
public async Task GotToRoot() public async Task GotToRoot()
{ {
await Tab.GoToRoot(); await RunFromCode(Tab.GoToRoot);
} }
public async Task GotToHome() public async Task GotToHome()
@@ -300,28 +329,42 @@ namespace FileTime.Avalonia.Application
var resolvedPath = await LocalContentProvider.GetByPath(path); var resolvedPath = await LocalContentProvider.GetByPath(path);
if (resolvedPath is IContainer homeFolder) if (resolvedPath is IContainer homeFolder)
{ {
await Tab.OpenContainer(homeFolder); await OpenContainer(homeFolder);
} }
} }
public async Task CreateContainer(string name) 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) 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) public async Task OpenContainer(IContainer container)
{ {
await Tab.OpenContainer(container); await RunFromCode(async () => await Tab.OpenContainer(container));
} }
public async Task MarkCurrentItem() public async Task MarkCurrentItem()
{ {
await _tabState.MakrCurrentItem(); await _tabState.MarkCurrentItem();
} }
public async Task UpdateMarkedItems(ContainerViewModel containerViewModel, CancellationToken token = default) public async Task UpdateMarkedItems(ContainerViewModel containerViewModel, CancellationToken token = default)

View File

@@ -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; else if (value is double valueDouble && (parameter is double parameterDouble || double.TryParse(parameter?.ToString(), out parameterDouble))) return valueDouble != parameterDouble;
return value != parameter; 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; 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 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; return value == parameter;

View File

@@ -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();
}
}
}

View File

@@ -1,21 +1,20 @@
using FileTime.Core.Interactions; using FileTime.Core.Interactions;
using System;
using System.Collections.Generic;
using System.Text;
namespace FileTime.Avalonia.Misc namespace FileTime.Avalonia.Misc
{ {
public class InputElementWrapper public class InputElementWrapper
{ {
public InputElement InputElement { get; } public InputElement InputElement { get; }
public string Value { get; set; } public string Value { get; set; }
public char? PasswordChar { get; set; }
public InputElementWrapper(InputElement inputElement, string? defaultValue = null) public InputElementWrapper(InputElement inputElement, string? defaultValue = null)
{ {
InputElement = inputElement; InputElement = inputElement;
Value = defaultValue ?? ""; Value = defaultValue ?? "";
PasswordChar = inputElement.InputType == InputType.Password ? '*' : null;
} }
} }
} }

View File

@@ -0,0 +1,9 @@
namespace FileTime.Avalonia.Models
{
public enum ElementPreviewMode
{
Unknown,
Text,
Empty
}
}

View File

@@ -60,6 +60,12 @@ namespace FileTime.Avalonia
{ {
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args); BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
} }
#if DEBUG
catch
{
throw;
}
#else
catch (Exception e) catch (Exception e)
{ {
var message = $"Ciritcal error cought in {nameof(Program)}"; var message = $"Ciritcal error cought in {nameof(Program)}";
@@ -79,6 +85,7 @@ namespace FileTime.Avalonia
using var streamWriter = new StreamWriter(fileWriter); using var streamWriter = new StreamWriter(fileWriter);
streamWriter.WriteLine(DateTime.Now.ToString() + ": " + message + "\n" + e.ToString() + "\n\n"); streamWriter.WriteLine(DateTime.Now.ToString() + ": " + message + "\n" + e.ToString() + "\n\n");
} }
#endif
} }
// Avalonia configuration, don't remove; also used by visual designer. // Avalonia configuration, don't remove; also used by visual designer.

View File

@@ -133,12 +133,13 @@ namespace FileTime.Avalonia.ViewModels
public async Task Init(bool initializeChildren = true, CancellationToken token = default) 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) 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)}.")] [Obsolete($"Use the parametrizable version of {nameof(Refresh)}.")]
@@ -146,7 +147,7 @@ namespace FileTime.Avalonia.ViewModels
{ {
await Refresh(true); 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; if (_isRefreshing) return;
@@ -157,18 +158,13 @@ namespace FileTime.Avalonia.ViewModels
{ {
_isRefreshing = true; _isRefreshing = true;
var containers = (await _container.GetContainers())!.Select(c => AdoptOrReuseOrCreateItem(c, (c2) => new ContainerViewModel(_newItemProcessor, this, c2, 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, (e2) => new ElementViewModel(e2, this, ItemNameConverterService))).ToList(); var elements = (await _container.GetElements())!.Select(e => AdoptOrReuseOrCreateItem(e, alloweReuse , (e2) => new ElementViewModel(e2, this, ItemNameConverterService))).ToList();
if (token.IsCancellationRequested) return; if (token.IsCancellationRequested) return;
Exceptions = new List<Exception>(_container.Exceptions); Exceptions = new List<Exception>(_container.Exceptions);
foreach (var containerToRemove in _containers.Except(containers))
{
containerToRemove?.Dispose();
}
if (initializeChildren) if (initializeChildren)
{ {
foreach (var container in containers) 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); Containers = new ObservableCollection<ContainerViewModel>(containers);
Elements = new ObservableCollection<ElementViewModel>(elements); Elements = new ObservableCollection<ElementViewModel>(elements);
Items = new ObservableCollection<IItemViewModel>(containers.Cast<IItemViewModel>().Concat(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) catch (Exception e)
{ {
@@ -197,13 +232,31 @@ namespace FileTime.Avalonia.ViewModels
_isRefreshing = false; _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); var itemToAdopt = ChildrenToAdopt.Find(i => i.Item == item);
if (itemToAdopt is TResult itemViewModel) return itemViewModel; if (itemToAdopt is TResult itemViewModel) return itemViewModel;
if (allowResuse)
{
var existingViewModel = _items?.FirstOrDefault(i => i.Item == item); var existingViewModel = _items?.FirstOrDefault(i => i.Item == item);
if (existingViewModel is TResult itemViewModelToReuse) return itemViewModelToReuse; if (existingViewModel is TResult itemViewModelToReuse) return itemViewModelToReuse;
}
return generator(item); return generator(item);
} }
@@ -221,26 +274,26 @@ namespace FileTime.Avalonia.ViewModels
} }
} }
_containers = new ObservableCollection<ContainerViewModel>(); _containers.Clear();
_elements = new ObservableCollection<ElementViewModel>(); _elements.Clear();
_items = new ObservableCollection<IItemViewModel>(); _items.Clear();
} }
public async Task<ObservableCollection<ContainerViewModel>> GetContainers(CancellationToken token = default) 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; return _containers;
} }
public async Task<ObservableCollection<ElementViewModel>> GetElements(CancellationToken token = default) 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; return _elements;
} }
public async Task<ObservableCollection<IItemViewModel>> GetItems(CancellationToken token = default) 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; return _items;
} }

View File

@@ -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;
}
}
}
}

View File

@@ -74,10 +74,10 @@ namespace FileTime.Avalonia.ViewModels
private string _messageBoxText; private string _messageBoxText;
[Property] [Property]
private ObservableCollection<string> _popupTexts = new ObservableCollection<string>(); private ObservableCollection<string> _popupTexts = new();
[Property] [Property]
private bool _showAllShortcut; private bool _isAllShortcutVisible;
[Property] [Property]
private List<CommandBinding> _allShortcut; private List<CommandBinding> _allShortcut;
@@ -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; return Task.CompletedTask;
} }
@@ -757,14 +806,14 @@ namespace FileTime.Avalonia.ViewModels
_inputHandler = null; _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 if (key == Key.LeftAlt
|| key == Key.RightAlt || key == Key.RightAlt
|| key == Key.LeftShift || key == Key.LeftShift
|| key == Key.RightShift || key == Key.RightShift
|| key == Key.LeftCtrl || key == Key.LeftCtrl
|| key == Key.RightCtrl) return false; || key == Key.RightCtrl) return;
NoCommandFound = false; NoCommandFound = false;
@@ -782,12 +831,14 @@ namespace FileTime.Avalonia.ViewModels
if (key == Key.Escape) if (key == Key.Escape)
{ {
ShowAllShortcut = false; IsAllShortcutVisible = false;
_previousKeys.Clear(); _previousKeys.Clear();
PossibleCommands = new(); PossibleCommands = new();
setHandled(true);
} }
else if (selectedCommandBinding != null) else if (selectedCommandBinding != null)
{ {
setHandled(true);
await selectedCommandBinding.InvokeAsync(); await selectedCommandBinding.InvokeAsync();
_previousKeys.Clear(); _previousKeys.Clear();
PossibleCommands = new(); PossibleCommands = new();
@@ -796,10 +847,11 @@ namespace FileTime.Avalonia.ViewModels
{ {
_previousKeys.Clear(); _previousKeys.Clear();
PossibleCommands = new(); PossibleCommands = new();
return false; return;
} }
else if (_previousKeys.Count == 2) else if (_previousKeys.Count == 2)
{ {
setHandled(true);
NoCommandFound = true; NoCommandFound = true;
_previousKeys.Clear(); _previousKeys.Clear();
PossibleCommands = new(); PossibleCommands = new();
@@ -817,6 +869,7 @@ namespace FileTime.Avalonia.ViewModels
{ {
PossibleCommands = possibleCommands; PossibleCommands = possibleCommands;
} }
setHandled(true);
} }
} }
else else
@@ -826,9 +879,10 @@ namespace FileTime.Avalonia.ViewModels
if (key == Key.Escape) if (key == Key.Escape)
{ {
if (ShowAllShortcut) setHandled(true);
if (IsAllShortcutVisible)
{ {
ShowAllShortcut = false; IsAllShortcutVisible = false;
} }
else else
{ {
@@ -839,12 +893,14 @@ namespace FileTime.Avalonia.ViewModels
{ {
if (AppState.RapidTravelText.Length > 0) if (AppState.RapidTravelText.Length > 0)
{ {
setHandled(true);
AppState.RapidTravelText = AppState.RapidTravelText.Substring(0, AppState.RapidTravelText.Length - 1); AppState.RapidTravelText = AppState.RapidTravelText.Substring(0, AppState.RapidTravelText.Length - 1);
updateRapidTravelFilter = true; updateRapidTravelFilter = true;
} }
} }
else if (keyString.Length == 1) else if (keyString.Length == 1)
{ {
setHandled(true);
AppState.RapidTravelText += keyString.ToString().ToLower(); AppState.RapidTravelText += keyString.ToString().ToLower();
updateRapidTravelFilter = true; updateRapidTravelFilter = true;
} }
@@ -854,12 +910,8 @@ namespace FileTime.Avalonia.ViewModels
var selectedCommandBinding = _universalCommandBindings.Find(c => AreKeysEqual(c.Keys, currentKeyAsList)); var selectedCommandBinding = _universalCommandBindings.Find(c => AreKeysEqual(c.Keys, currentKeyAsList));
if (selectedCommandBinding != null) if (selectedCommandBinding != null)
{ {
setHandled(true);
await selectedCommandBinding.InvokeAsync(); 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) private void ReadInputs(List<Core.Interactions.InputElement> inputs, Action inputHandler)
@@ -1144,7 +1189,12 @@ namespace FileTime.Avalonia.ViewModels
"show all shortcut", "show all shortcut",
FileTime.App.Core.Command.Commands.Dummy, FileTime.App.Core.Command.Commands.Dummy,
new KeyWithModifiers[] { new KeyWithModifiers(Key.F1) }, 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 //TODO REMOVE
new CommandBinding( new CommandBinding(
"open in default file browser", "open in default file browser",

View File

@@ -6,12 +6,12 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:FileTime.Avalonia.ViewModels" xmlns:vm="using:FileTime.Avalonia.ViewModels"
xmlns:local="using:FileTime.Avalonia.Views" xmlns:local="using:FileTime.Avalonia.Views"
xmlns:models="using:FileTime.Avalonia.Models"
Title="FileTime" Title="FileTime"
d:DesignHeight="450" d:DesignHeight="450"
d:DesignWidth="800" d:DesignWidth="800"
Icon="/Assets/filetime.ico" Icon="/Assets/filetime.ico"
InputElement.KeyDown="OnKeyDown" InputElement.KeyDown="OnKeyDown"
InputElement.KeyUp="OnKeyUp"
TransparencyLevelHint="Blur" TransparencyLevelHint="Blur"
Background="Transparent" Background="Transparent"
ExtendClientAreaToDecorationsHint="True" ExtendClientAreaToDecorationsHint="True"
@@ -269,123 +269,65 @@
VerticalAlignment="Stretch" VerticalAlignment="Stretch"
Fill="{DynamicResource ContentSeparatorBrush}" /> Fill="{DynamicResource ContentSeparatorBrush}" />
<Grid Grid.Column="4" IsVisible="{Binding AppState.SelectedTab.ChildContainer,Converter={StaticResource IsNotNullConverter}}"> <Grid Grid.Column="4">
<ListBox <Grid IsVisible="{Binding AppState.SelectedTab.ChildContainer,Converter={StaticResource IsNotNullConverter}}">
Classes="ContentListView" <ListBox
IsEnabled="False" Classes="ContentListView"
x:Name="ChildItems" IsEnabled="False"
Items="{Binding AppState.SelectedTab.ChildContainer.Items}" x:Name="ChildItems"
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}"> Items="{Binding AppState.SelectedTab.ChildContainer.Items}"
<ListBox.ItemTemplate> IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
<DataTemplate> <ListBox.ItemTemplate>
<local:ItemView/> <DataTemplate>
</DataTemplate> <local:ItemView/>
</ListBox.ItemTemplate> </DataTemplate>
</ListBox> </ListBox.ItemTemplate>
</ListBox>
<Grid <Grid IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
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}">
<TextBlock <TextBlock
Margin="0,0,0,10" x:Name="ChildEmpty"
Margin="10"
HorizontalAlignment="Center" HorizontalAlignment="Center"
TextWrapping="Wrap" FontWeight="Bold"
Text="There were some errors while opening container." Foreground="{DynamicResource ErrorBrush}"
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}"> <Grid
<ItemsRepeater.ItemTemplate> RowDefinitions="Auto, Auto"
<DataTemplate> IsVisible="{Binding AppState.SelectedTab.ChildContainer.Exceptions.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
<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>
<TextBlock <TextBlock
HorizontalAlignment="Left" Margin="0,0,0,10"
VerticalAlignment="Center" HorizontalAlignment="Center"
Text="{Binding InputElement.Text}" /> TextWrapping="Wrap"
<TextBox Text="There were some errors while opening container."
AttachedToVisualTree="InputText_AttachedToVisualTree" Foreground="{DynamicResource ErrorBrush}" />
Grid.Column="1"
GotFocus="InputText_GotFocus" <ItemsRepeater Grid.Row="1" Items="{Binding AppState.SelectedTab.ChildContainer.Exceptions}">
LostFocus="InputText_LostFocus" <ItemsRepeater.ItemTemplate>
KeyDown="InputText_KeyDown" <DataTemplate>
Text="{Binding Value, Mode=TwoWay}" /> <TextBlock Text="{Binding, Converter={StaticResource ExceptionToStringConverter}}"/>
</DataTemplate>
</ItemsRepeater.ItemTemplate>
</ItemsRepeater>
</Grid> </Grid>
</DataTemplate> </Grid>
</ItemsControl.ItemTemplate> </Grid>
</ItemsControl>
<StackPanel
Grid.Row="1"
Orientation="Horizontal">
<Button
Command="{Binding ProcessInputsCommand}"
Content="Ok" />
<Button
Command="{Binding CancelInputsCommand}"
Content="Cancel" />
</StackPanel>
</Grid>
<Grid <Grid IsVisible="{Binding AppState.SelectedTab.ElementPreview, Converter={StaticResource IsNotNullConverter}}">
HorizontalAlignment="Center" <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}}"/>
VerticalAlignment="Center" <TextBlock HorizontalAlignment="Center" Text="Empty" IsVisible="{Binding AppState.SelectedTab.ElementPreview.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static models:ElementPreviewMode.Empty}}"/>
IsVisible="{Binding MessageBoxText, Converter={StaticResource IsNotNullConverter}}"> <ScrollViewer IsVisible="{Binding AppState.SelectedTab.ElementPreview.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static models:ElementPreviewMode.Text}}">
<Grid.RowDefinitions> <TextBox
<RowDefinition Height="Auto" /> IsReadOnly="True"
<RowDefinition Height="Auto" /> Text="{Binding AppState.SelectedTab.ElementPreview.TextContent}" />
</Grid.RowDefinitions> </ScrollViewer>
</Grid>
<TextBlock Text="{Binding MessageBoxText}"/> </Grid>
<StackPanel
Grid.Row="1"
Orientation="Horizontal">
<Button
Command="{Binding ProcessMessageBoxCommand}"
Content="Yes" />
<Button
Command="{Binding CancelMessageBoxCommand}"
Content="No" />
</StackPanel>
</Grid> </Grid>
<ItemsRepeater Items="{Binding PopupTexts}" Margin="0,0,0,20" HorizontalAlignment="Center" VerticalAlignment="Bottom" IsVisible="{Binding PopupTexts.Count,Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}"> <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>
</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"> <Grid RowDefinitions="Auto, *" Margin="30,10">
<TextBlock Text="Shortcuts" Margin="0,0,0,20"/> <TextBlock Text="Shortcuts" Margin="0,0,0,20"/>
<ScrollViewer <ScrollViewer

View File

@@ -45,19 +45,11 @@ namespace FileTime.Avalonia.Views
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
} }
public async void OnKeyDown(object sender, KeyEventArgs e) public void OnKeyDown(object sender, KeyEventArgs e)
{ {
if (_inputElementWrapper == null) if (_inputElementWrapper == null)
{ {
e.Handled = e.Handled || await ViewModel!.ProcessKeyDown(e.Key, e.KeyModifiers); ViewModel!.ProcessKeyDown(e.Key, e.KeyModifiers, h => e.Handled = h);
}
}
public async void OnKeyUp(object sender, KeyEventArgs e)
{
if (_inputElementWrapper == null)
{
e.Handled = e.Handled || await ViewModel!.ProcessKeyUp(e.Key, e.KeyModifiers);
} }
} }
@@ -87,7 +79,7 @@ namespace FileTime.Avalonia.Views
private void InputText_LostFocus(object sender, RoutedEventArgs e) 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; _inputElementWrapper = null;
} }

View File

@@ -33,6 +33,8 @@ namespace FileTime.Providers.Local
public bool SupportsDirectoryLevelSoftDelete => false; public bool SupportsDirectoryLevelSoftDelete => false;
public bool IsDisposed => false;
public LocalContentProvider(ILogger<LocalContentProvider> logger) public LocalContentProvider(ILogger<LocalContentProvider> logger)
{ {
_logger = logger; _logger = logger;
@@ -100,5 +102,7 @@ namespace FileTime.Providers.Local
public Task Rename(string newName) => throw new NotSupportedException(); public Task Rename(string newName) => throw new NotSupportedException();
public Task<bool> CanOpen() => Task.FromResult(true); public Task<bool> CanOpen() => Task.FromResult(true);
public void Dispose() { }
} }
} }

View File

@@ -30,6 +30,8 @@ namespace FileTime.Providers.Local
private readonly LocalFolder _parent; private readonly LocalFolder _parent;
public bool IsDisposed { get; private set; }
public LocalFile(FileInfo file, LocalFolder parent, IContentProvider contentProvider) public LocalFile(FileInfo file, LocalFolder parent, IContentProvider contentProvider)
{ {
_parent = parent; _parent = parent;
@@ -80,5 +82,10 @@ namespace FileTime.Providers.Local
} }
public IContainer? GetParent() => _parent; 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;
} }
} }

View File

@@ -11,7 +11,7 @@ namespace FileTime.Providers.Local
private IReadOnlyList<IItem>? _items; private IReadOnlyList<IItem>? _items;
private IReadOnlyList<IContainer>? _containers; private IReadOnlyList<IContainer>? _containers;
private IReadOnlyList<IElement>? _elements; private IReadOnlyList<IElement>? _elements;
private List<Exception> _exceptions; private readonly List<Exception> _exceptions;
private readonly IContainer? _parent; private readonly IContainer? _parent;
public bool IsHidden => (Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden; public bool IsHidden => (Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
@@ -34,6 +34,8 @@ namespace FileTime.Providers.Local
public DateTime CreatedAt => Directory.CreationTime; public DateTime CreatedAt => Directory.CreationTime;
public IReadOnlyList<Exception> Exceptions { get; } public IReadOnlyList<Exception> Exceptions { get; }
public bool IsDisposed { get; private set; }
public bool SupportsDirectoryLevelSoftDelete { get; } public bool SupportsDirectoryLevelSoftDelete { get; }
public LocalFolder(DirectoryInfo directory, LocalContentProvider contentProvider, IContainer? parent) 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<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>(); _containers = new List<IContainer>();
_elements = new List<IElement>(); _elements = new List<IElement>();
_items = new List<IItem>(); _items = new List<IItem>();
@@ -68,13 +78,13 @@ namespace FileTime.Providers.Local
try 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(); _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(); _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) catch (Exception e)
{ {
@@ -82,9 +92,7 @@ namespace FileTime.Providers.Local
} }
_items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly(); _items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly();
Refreshed?.InvokeAsync(this, AsyncEventArgs.Empty, token); if (Refreshed != null) await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
return Task.CompletedTask;
} }
public async Task<IReadOnlyList<IItem>?> GetItems(CancellationToken token = default) 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 Task<bool> CanOpen() => Task.FromResult(true);
public void Dispose()
{
_items = null;
_containers = null;
_elements = null;
IsDisposed = true;
}
} }
} }

View File

@@ -31,6 +31,8 @@ namespace FileTime.Providers.Smb
public bool SupportsDirectoryLevelSoftDelete => false; public bool SupportsDirectoryLevelSoftDelete => false;
public bool IsDisposed => false;
public SmbContentProvider(IInputInterface inputInterface) public SmbContentProvider(IInputInterface inputInterface)
{ {
_rootContainers = new List<IContainer>(); _rootContainers = new List<IContainer>();
@@ -102,5 +104,7 @@ namespace FileTime.Providers.Smb
public Task Rename(string newName) => throw new NotSupportedException(); public Task Rename(string newName) => throw new NotSupportedException();
public Task<bool> CanOpen() => Task.FromResult(true); public Task<bool> CanOpen() => Task.FromResult(true);
public void Dispose() { }
} }
} }

View File

@@ -19,6 +19,8 @@ namespace FileTime.Providers.Smb
public IContentProvider Provider { get; } public IContentProvider Provider { get; }
private IContainer _parent; private IContainer _parent;
public bool IsDisposed { get; private set; }
public SmbFile(string name, SmbContentProvider provider, IContainer parent) public SmbFile(string name, SmbContentProvider provider, IContainer parent)
{ {
Name = name; Name = name;
@@ -43,5 +45,9 @@ namespace FileTime.Providers.Smb
} }
public IContainer? GetParent() => _parent; 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;
} }
} }

View File

@@ -29,6 +29,8 @@ namespace FileTime.Providers.Smb
public AsyncEventHandler Refreshed { get; } = new(); public AsyncEventHandler Refreshed { get; } = new();
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly(); public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
public bool IsDisposed { get; private set; }
public bool SupportsDirectoryLevelSoftDelete => false; public bool SupportsDirectoryLevelSoftDelete => false;
public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent) public SmbFolder(string name, SmbContentProvider contentProvider, SmbShare smbShare, IContainer parent)
@@ -103,6 +105,14 @@ namespace FileTime.Providers.Smb
_containers = containers.AsReadOnly(); _containers = containers.AsReadOnly();
_elements = elements.AsReadOnly(); _elements = elements.AsReadOnly();
if (_items != null)
{
foreach (var item in _items)
{
item.Dispose();
}
}
_items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly(); _items = _containers.Cast<IItem>().Concat(_elements).ToList().AsReadOnly();
await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token); await Refreshed.InvokeAsync(this, AsyncEventArgs.Empty, token);
} }
@@ -123,5 +133,7 @@ namespace FileTime.Providers.Smb
return _elements; return _elements;
} }
public Task<bool> CanOpen() => Task.FromResult(true); public Task<bool> CanOpen() => Task.FromResult(true);
public void Dispose() => IsDisposed = true;
} }
} }

View File

@@ -40,6 +40,8 @@ namespace FileTime.Providers.Smb
public bool SupportsDirectoryLevelSoftDelete => false; public bool SupportsDirectoryLevelSoftDelete => false;
public bool IsDisposed => false;
public SmbServer(string path, SmbContentProvider contentProvider, IInputInterface inputInterface) public SmbServer(string path, SmbContentProvider contentProvider, IInputInterface inputInterface)
{ {
_inputInterface = inputInterface; _inputInterface = inputInterface;
@@ -97,7 +99,7 @@ namespace FileTime.Providers.Smb
_shares = shares.ConvertAll(s => new SmbShare(s, Provider, this, _smbClientContext)).AsReadOnly(); _shares = shares.ConvertAll(s => new SmbShare(s, Provider, this, _smbClientContext)).AsReadOnly();
_items = _shares.Cast<IItem>().ToList().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); 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 Rename(string newName) => throw new NotSupportedException();
public Task<bool> CanOpen() => Task.FromResult(true); public Task<bool> CanOpen() => Task.FromResult(true);
public void Dispose() { }
} }
} }

View File

@@ -29,6 +29,8 @@ namespace FileTime.Providers.Smb
public AsyncEventHandler Refreshed { get; } = new(); public AsyncEventHandler Refreshed { get; } = new();
public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly(); public IReadOnlyList<Exception> Exceptions { get; } = new List<Exception>().AsReadOnly();
public bool IsDisposed => false;
public bool SupportsDirectoryLevelSoftDelete => false; public bool SupportsDirectoryLevelSoftDelete => false;
public SmbShare(string name, SmbContentProvider contentProvider, IContainer parent, SmbClientContext smbClientContext) public SmbShare(string name, SmbContentProvider contentProvider, IContainer parent, SmbClientContext smbClientContext)
@@ -111,6 +113,14 @@ namespace FileTime.Providers.Smb
} }
catch { } catch { }
if (_items != null)
{
foreach (var item in _items)
{
item.Dispose();
}
}
_containers = containers.AsReadOnly(); _containers = containers.AsReadOnly();
_elements = elements.AsReadOnly(); _elements = elements.AsReadOnly();
@@ -160,5 +170,7 @@ namespace FileTime.Providers.Smb
public Task Rename(string newName) => throw new NotSupportedException(); public Task Rename(string newName) => throw new NotSupportedException();
public Task<bool> CanOpen() => Task.FromResult(true); public Task<bool> CanOpen() => Task.FromResult(true);
public void Dispose() { }
} }
} }