TimeTravel
This commit is contained in:
@@ -15,12 +15,18 @@
|
||||
<Color x:Key="ItemBackgroundColor">#00000000</Color>
|
||||
<Color x:Key="AlternativeItemBackgroundColor">#10000000</Color>
|
||||
<Color x:Key="SelectedItemBackgroundColor">#93a1a1</Color>
|
||||
<Color x:Key="MarkedItemBackgroundColor">#00000000</Color>
|
||||
<Color x:Key="MarkedAlternativeItemBackgroundColor">#10000000</Color>
|
||||
<Color x:Key="MarkedSelectedItemBackgroundColor">#b58900</Color>
|
||||
|
||||
<Color x:Key="ForegroundColor">#93a1a1</Color>
|
||||
<Color x:Key="AccentForegroundColor">#268bd2</Color>
|
||||
<Color x:Key="LightForegroundColor">#7793a1a1</Color>
|
||||
<Color x:Key="AlternativeForegroundColor">#93a1a1</Color>
|
||||
<Color x:Key="AlternativeItemForegroundColor">#93a1a1</Color>
|
||||
<Color x:Key="SelectedItemForegroundColor">#073642</Color>
|
||||
<Color x:Key="MarkedItemForegroundColor">#b58900</Color>
|
||||
<Color x:Key="MarkedAlternativeItemForegroundColor">#b58900</Color>
|
||||
<Color x:Key="MarkedSelectedItemForegroundColor">#002b36</Color>
|
||||
|
||||
<Color x:Key="ErrorColor">#dc322f</Color>
|
||||
|
||||
@@ -48,6 +54,15 @@
|
||||
<SolidColorBrush
|
||||
x:Key="SelectedItemBackgroundBrush"
|
||||
Color="{DynamicResource SelectedItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedItemBackgroundBrush"
|
||||
Color="{DynamicResource MarkedItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedSelectedItemBackgroundBrush"
|
||||
Color="{DynamicResource MarkedSelectedItemBackgroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedAlternativeItemBackgroundBrush"
|
||||
Color="{DynamicResource MarkedAlternativeItemBackgroundColor}" />
|
||||
|
||||
<SolidColorBrush
|
||||
x:Key="ForegroundBrush"
|
||||
@@ -59,11 +74,20 @@
|
||||
x:Key="LightForegroundBrush"
|
||||
Color="{DynamicResource LightForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="AlternativeForegroundBrush"
|
||||
Color="{DynamicResource AlternativeForegroundColor}" />
|
||||
x:Key="AlternativeItemForegroundBrush"
|
||||
Color="{DynamicResource AlternativeItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="SelectedItemForegroundBrush"
|
||||
Color="{DynamicResource SelectedItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedItemForegroundBrush"
|
||||
Color="{DynamicResource MarkedItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedAlternativeItemForegroundBrush"
|
||||
Color="{DynamicResource MarkedAlternativeItemForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="MarkedSelectedItemForegroundBrush"
|
||||
Color="{DynamicResource MarkedSelectedItemForegroundColor}" />
|
||||
|
||||
<SolidColorBrush
|
||||
x:Key="ErrorBrush"
|
||||
@@ -79,14 +103,23 @@
|
||||
<converters:ItemViewModeToBrushConverter
|
||||
x:Key="ItemViewModeToForegroundConverter"
|
||||
DefaultBrush="{StaticResource ForegroundBrush}"
|
||||
AlternativeBrush="{StaticResource AlternativeForegroundBrush}"
|
||||
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"/>
|
||||
AlternativeBrush="{StaticResource AlternativeItemForegroundBrush}"
|
||||
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"
|
||||
MarkedBrush="{StaticResource MarkedItemForegroundBrush}"
|
||||
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemForegroundBrush}"
|
||||
MarkedSelectedBrush="{StaticResource MarkedSelectedItemForegroundBrush}"/>
|
||||
<converters:ItemViewModeToBrushConverter
|
||||
x:Key="ItemViewModeToBackgroundConverter"
|
||||
DefaultBrush="{StaticResource ItemBackgroundBrush}"
|
||||
AlternativeBrush="{StaticResource AlternativeItemBackgroundBrush}"
|
||||
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"/>
|
||||
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"
|
||||
MarkedBrush="{StaticResource MarkedItemBackgroundBrush}"
|
||||
MarkedAlternativeBrush="{StaticResource MarkedAlternativeItemBackgroundBrush}"
|
||||
MarkedSelectedBrush="{StaticResource MarkedSelectedItemBackgroundBrush}"/>
|
||||
<converters:ContextMenuGenerator x:Key="ContextMenuGenerator"/>
|
||||
<converters:ItemToImageConverter x:Key="ItemToImageConverter"/>
|
||||
<converters:IsNullConverter x:Key="IsNullConverter"/>
|
||||
<converters:IsNullConverter x:Key="IsNotNullConverter" Inverse="true"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace FileTime.Avalonia
|
||||
.RegisterDefaultServices()
|
||||
.AddViewModels()
|
||||
.AddServices()
|
||||
.RegisterCommandHandlers()
|
||||
.BuildServiceProvider();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
using FileTime.Core.Components;
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using MvvmGen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using FileTime.App.Core.Tab;
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
@@ -11,7 +13,7 @@ namespace FileTime.Avalonia.Application
|
||||
public partial class AppState
|
||||
{
|
||||
[Property]
|
||||
private ObservableCollection<TabContainer> _tabs = new ObservableCollection<TabContainer>();
|
||||
private ObservableCollection<TabContainer> _tabs = new();
|
||||
|
||||
[Property]
|
||||
private TabContainer _selectedTab;
|
||||
@@ -24,7 +26,66 @@ namespace FileTime.Avalonia.Application
|
||||
|
||||
partial void OnInitialize()
|
||||
{
|
||||
_tabs.CollectionChanged += (o, e) => SelectedTab ??= Tabs.Count > 0 ? Tabs[0] : null;
|
||||
_tabs.CollectionChanged += TabsChanged;
|
||||
}
|
||||
|
||||
private void TabsChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
SelectedTab ??= Tabs.Count > 0 ? Tabs[0] : null;
|
||||
|
||||
List<TabContainer> itemsAdded = new();
|
||||
List<TabContainer> itemsRemoved = new();
|
||||
if (e.NewItems != null && e.OldItems != null)
|
||||
{
|
||||
itemsAdded.AddRange(e.NewItems.Cast<TabContainer>().Except(e.OldItems.Cast<TabContainer>()));
|
||||
itemsRemoved.AddRange(e.OldItems.Cast<TabContainer>().Except(e.NewItems.Cast<TabContainer>()));
|
||||
}
|
||||
else if (e.NewItems != null)
|
||||
{
|
||||
itemsAdded.AddRange(e.NewItems.Cast<TabContainer>());
|
||||
}
|
||||
else if (e.OldItems != null)
|
||||
{
|
||||
itemsRemoved.AddRange(e.OldItems.Cast<TabContainer>());
|
||||
}
|
||||
|
||||
foreach (var item in itemsAdded)
|
||||
{
|
||||
item.TabState.ItemMarked.Add(TabItemMarked);
|
||||
item.TabState.ItemUnmarked.Add(TabItemUnmarked);
|
||||
}
|
||||
|
||||
foreach (var item in itemsRemoved)
|
||||
{
|
||||
item.TabState.ItemMarked.Remove(TabItemMarked);
|
||||
item.TabState.ItemUnmarked.Remove(TabItemUnmarked);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TabItemMarked(TabState tabState, AbsolutePath item)
|
||||
{
|
||||
var tabContainer = Tabs.FirstOrDefault(t => t.TabState == tabState);
|
||||
if (tabContainer != null)
|
||||
{
|
||||
var item2 = (await tabContainer.CurrentLocation.GetItems()).FirstOrDefault(i => i.Item.FullName == item.Path);
|
||||
if (item2 != null)
|
||||
{
|
||||
item2.IsMarked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TabItemUnmarked(TabState tabState, AbsolutePath item)
|
||||
{
|
||||
var tabContainer = Tabs.FirstOrDefault(t => t.TabState == tabState);
|
||||
if (tabContainer != null)
|
||||
{
|
||||
var item2 = (await tabContainer.CurrentLocation.GetItems()).FirstOrDefault(i => i.Item.FullName == item.Path);
|
||||
if (item2 != null)
|
||||
{
|
||||
item2.IsMarked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
public interface INewItemProcessor
|
||||
{
|
||||
Task UpdateMarkedItems(ContainerViewModel containerViewModel);
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,11 @@ using FileTime.Avalonia.Services;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.App.Core.Tab;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
{
|
||||
@@ -18,8 +18,11 @@ namespace FileTime.Avalonia.Application
|
||||
[Inject(typeof(ItemNameConverterService))]
|
||||
[Inject(typeof(LocalContentProvider))]
|
||||
[Inject(typeof(Tab))]
|
||||
public partial class TabContainer
|
||||
public partial class TabContainer : INewItemProcessor
|
||||
{
|
||||
[Property]
|
||||
private TabState _tabState;
|
||||
|
||||
[Property]
|
||||
private ContainerViewModel _parent;
|
||||
|
||||
@@ -45,25 +48,33 @@ namespace FileTime.Avalonia.Application
|
||||
if (_selectedItem != value)// && value != null
|
||||
{
|
||||
_selectedItem = value;
|
||||
|
||||
OnPropertyChanged("SelectedItem");
|
||||
SelectedItemChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnInitialize()
|
||||
{
|
||||
_tabState = new TabState(Tab);
|
||||
}
|
||||
|
||||
public async Task Init(int tabNumber)
|
||||
{
|
||||
TabNumber = tabNumber;
|
||||
Tab.CurrentLocationChanged.Add(Tab_CurrentLocationChanged);
|
||||
Tab.CurrentSelectedItemChanged.Add(Tab_CurrentSelectedItemChanged);
|
||||
|
||||
CurrentLocation = new ContainerViewModel(await Tab.GetCurrentLocation(), ItemNameConverterService);
|
||||
var currentLocation = await Tab.GetCurrentLocation();
|
||||
var parent = GenerateParent(currentLocation);
|
||||
CurrentLocation = new ContainerViewModel(this, parent, currentLocation, ItemNameConverterService);
|
||||
await CurrentLocation.Init();
|
||||
|
||||
var parent = (await Tab.GetCurrentLocation()).GetParent();
|
||||
if (parent != null)
|
||||
{
|
||||
Parent = new ContainerViewModel(parent, ItemNameConverterService);
|
||||
parent.ChildrenToAdopt.Add(CurrentLocation);
|
||||
Parent = parent;
|
||||
await Parent.Init();
|
||||
}
|
||||
else
|
||||
@@ -74,16 +85,28 @@ namespace FileTime.Avalonia.Application
|
||||
await UpdateCurrentSelectedItem();
|
||||
}
|
||||
|
||||
private ContainerViewModel? GenerateParent(IContainer? container, bool recursive = false)
|
||||
{
|
||||
var parentContainer = container?.GetParent();
|
||||
if (parentContainer == null) return null;
|
||||
var parentParent = recursive ? GenerateParent(parentContainer.GetParent(), recursive) : null;
|
||||
|
||||
var parent = new ContainerViewModel(this, parentParent, parentContainer, ItemNameConverterService);
|
||||
parentParent?.ChildrenToAdopt.Add(parent);
|
||||
return parent;
|
||||
}
|
||||
|
||||
private async Task Tab_CurrentLocationChanged(object? sender, AsyncEventArgs e)
|
||||
{
|
||||
var currentLocation = await Tab.GetCurrentLocation();
|
||||
CurrentLocation = new ContainerViewModel(currentLocation, ItemNameConverterService);
|
||||
var parent = GenerateParent(currentLocation);
|
||||
CurrentLocation = new ContainerViewModel(this, parent, currentLocation, ItemNameConverterService);
|
||||
await CurrentLocation.Init();
|
||||
|
||||
var parent = currentLocation.GetParent();
|
||||
if (parent != null)
|
||||
{
|
||||
Parent = new ContainerViewModel(parent, ItemNameConverterService);
|
||||
parent.ChildrenToAdopt.Add(CurrentLocation);
|
||||
Parent = parent;
|
||||
await Parent.Init();
|
||||
}
|
||||
else
|
||||
@@ -97,39 +120,89 @@ namespace FileTime.Avalonia.Application
|
||||
await UpdateCurrentSelectedItem();
|
||||
}
|
||||
|
||||
private async Task UpdateCurrentSelectedItem()
|
||||
public async Task UpdateCurrentSelectedItem()
|
||||
{
|
||||
var tabCurrentSelectenItem = await Tab.GetCurrentSelectedItem();
|
||||
IItemViewModel? currentSelectenItem = null;
|
||||
if (tabCurrentSelectenItem == null)
|
||||
try
|
||||
{
|
||||
SelectedItem = null;
|
||||
ChildContainer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSelectenItem = (await _currentLocation.GetItems()).FirstOrDefault(i => i.Item.Name == tabCurrentSelectenItem.Name);
|
||||
if (currentSelectenItem is ContainerViewModel currentSelectedContainer)
|
||||
{
|
||||
SelectedItem = currentSelectedContainer;
|
||||
ChildContainer = currentSelectedContainer;
|
||||
}
|
||||
else if (currentSelectenItem is ElementViewModel element)
|
||||
{
|
||||
SelectedItem = element;
|
||||
ChildContainer = null;
|
||||
}
|
||||
else
|
||||
var tabCurrentSelectenItem = await Tab.GetCurrentSelectedItem();
|
||||
IItemViewModel? currentSelectenItem = null;
|
||||
if (tabCurrentSelectenItem == null)
|
||||
{
|
||||
SelectedItem = null;
|
||||
ChildContainer = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSelectenItem = (await _currentLocation.GetItems()).FirstOrDefault(i => i.Item.Name == tabCurrentSelectenItem.Name);
|
||||
if (currentSelectenItem is ContainerViewModel currentSelectedContainer)
|
||||
{
|
||||
SelectedItem = currentSelectedContainer;
|
||||
ChildContainer = currentSelectedContainer;
|
||||
}
|
||||
else if (currentSelectenItem is ElementViewModel element)
|
||||
{
|
||||
SelectedItem = element;
|
||||
ChildContainer = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectedItem = null;
|
||||
ChildContainer = null;
|
||||
}
|
||||
}
|
||||
|
||||
var items = await _currentLocation.GetItems();
|
||||
foreach (var item in items)
|
||||
var items = await _currentLocation.GetItems();
|
||||
if (items != null && items.Count > 0)
|
||||
{
|
||||
foreach (var item in items)
|
||||
{
|
||||
var isSelected = item == currentSelectenItem;
|
||||
item.IsSelected = isSelected;
|
||||
|
||||
if (isSelected)
|
||||
{
|
||||
var parent = item.Parent;
|
||||
while (parent != null)
|
||||
{
|
||||
parent.IsSelected = true;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var child = item;
|
||||
while (child is ContainerViewModel containerViewModel && containerViewModel.Container.IsLoaded)
|
||||
{
|
||||
var activeChildItem = await Tab.GetItemByLastPath(containerViewModel.Container);
|
||||
child = (await containerViewModel.GetItems()).FirstOrDefault(i => i.Item == activeChildItem);
|
||||
if (child != null)
|
||||
{
|
||||
child.IsSelected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
//INFO collection modified exception on: child = (await containerViewModel.GetItems()).FirstOrDefault(i => i.Item == activeChildItem);
|
||||
//TODO: handle or error message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var parent = _currentLocation;
|
||||
while (parent != null)
|
||||
{
|
||||
parent.IsSelected = true;
|
||||
parent = parent.Parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
item.IsSelected = item == currentSelectenItem;
|
||||
//INFO collection modified exception on: currentSelectenItem = (await _currentLocation.GetItems()).FirstOrDefault(i => i.Item.Name == tabCurrentSelectenItem.Name);
|
||||
//TODO: handle or error message
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,9 +285,32 @@ namespace FileTime.Avalonia.Application
|
||||
(await Tab.GetCurrentLocation())?.CreateContainer(name);
|
||||
}
|
||||
|
||||
public async Task CreateElement(string name)
|
||||
{
|
||||
(await Tab.GetCurrentLocation())?.CreateElement(name);
|
||||
}
|
||||
|
||||
public async Task OpenContainer(IContainer container)
|
||||
{
|
||||
await Tab.OpenContainer(container);
|
||||
}
|
||||
|
||||
public async Task MarkCurrentItem()
|
||||
{
|
||||
await _tabState.MakrCurrentItem();
|
||||
}
|
||||
|
||||
public async Task UpdateMarkedItems(ContainerViewModel containerViewModel)
|
||||
{
|
||||
if (containerViewModel == CurrentLocation && containerViewModel.Container.IsLoaded)
|
||||
{
|
||||
var selectedItems = TabState.GetCurrentMarkedItems(containerViewModel.Container);
|
||||
|
||||
foreach (var item in await containerViewModel.GetItems())
|
||||
{
|
||||
item.IsMarked = selectedItems.Any(c => c.Path == item.Item.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
src/GuiApp/FileTime.Avalonia/Converters/IsNullConverter.cs
Normal file
23
src/GuiApp/FileTime.Avalonia/Converters/IsNullConverter.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Avalonia.Data.Converters;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class IsNullConverter : IValueConverter
|
||||
{
|
||||
public bool Inverse { get; set; }
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
var result = value == null;
|
||||
if (Inverse) result = !result;
|
||||
return result;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Svg.Skia;
|
||||
using FileTime.Avalonia.IconProviders;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class ItemToImageConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value == null) return null;
|
||||
|
||||
IIconProvider converter = new MaterialIconProvider();
|
||||
|
||||
IItem item = value switch
|
||||
{
|
||||
ContainerViewModel container => container.Container,
|
||||
ElementViewModel element => element.Element,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
|
||||
var path = converter.GetImage(item)!;
|
||||
var source = SvgSource.Load<SvgSource>("avares://FileTime.Avalonia" + path, null);
|
||||
return new SvgImage { Source = source };
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ namespace FileTime.Avalonia.Converters
|
||||
public Brush? DefaultBrush { get; set; }
|
||||
public Brush? AlternativeBrush { get; set; }
|
||||
public Brush? SelectedBrush { get; set; }
|
||||
public Brush? MarkedBrush { get; set; }
|
||||
public Brush? MarkedSelectedBrush { get; set; }
|
||||
public Brush? MarkedAlternativeBrush { get; set; }
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
@@ -21,6 +24,9 @@ namespace FileTime.Avalonia.Converters
|
||||
ItemViewMode.Default => DefaultBrush,
|
||||
ItemViewMode.Alternative => AlternativeBrush,
|
||||
ItemViewMode.Selected => SelectedBrush,
|
||||
ItemViewMode.Marked => MarkedBrush,
|
||||
ItemViewMode.MarkedSelected => MarkedSelectedBrush,
|
||||
ItemViewMode.MarkedAlternative => MarkedAlternativeBrush,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using FileTime.Core.Models;
|
||||
|
||||
namespace FileTime.Avalonia.IconProviders
|
||||
{
|
||||
public interface IIconProvider
|
||||
{
|
||||
string GetImage(IItem item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Providers.Local;
|
||||
using System.Linq;
|
||||
|
||||
namespace FileTime.Avalonia.IconProviders
|
||||
{
|
||||
public class MaterialIconProvider : IIconProvider
|
||||
{
|
||||
public string GetImage(IItem item)
|
||||
{
|
||||
var icon = "file.svg";
|
||||
if (item is IContainer)
|
||||
{
|
||||
icon = "folder.svg";
|
||||
}
|
||||
else if (item is IElement element)
|
||||
{
|
||||
if(element is LocalFile localFile && element.FullName.EndsWith(".svg"))
|
||||
{
|
||||
return localFile.File.FullName;
|
||||
}
|
||||
icon = !element.Name.Contains('.')
|
||||
? icon
|
||||
: element.Name.Split('.').Last() switch
|
||||
{
|
||||
"cs" => "csharp.svg",
|
||||
_ => icon
|
||||
};
|
||||
}
|
||||
return "/Assets/material/" + icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,9 +12,10 @@ namespace FileTime.Avalonia.Misc
|
||||
|
||||
public string Value { get; set; }
|
||||
|
||||
public InputElementWrapper(InputElement inputElement)
|
||||
public InputElementWrapper(InputElement inputElement, string? defaultValue = null)
|
||||
{
|
||||
InputElement = inputElement;
|
||||
Value = defaultValue ?? "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using FileTime.Avalonia.Application;
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FileTime.Avalonia.Services
|
||||
@@ -20,9 +21,8 @@ namespace FileTime.Avalonia.Services
|
||||
{
|
||||
var nameLeft = itemViewModel.Item.Name;
|
||||
|
||||
while (nameLeft.ToLower().Contains(rapidTravelText))
|
||||
while (nameLeft.ToLower().IndexOf(rapidTravelText, StringComparison.Ordinal) is int rapidTextStart && rapidTextStart != -1)
|
||||
{
|
||||
var rapidTextStart = nameLeft.ToLower().IndexOf(rapidTravelText);
|
||||
var before = rapidTextStart > 0 ? nameLeft.Substring(0, rapidTextStart) : null;
|
||||
var rapidTravel = nameLeft.Substring(rapidTextStart, rapidTravelText.Length);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using FileTime.Avalonia.Application;
|
||||
using FileTime.Avalonia.Services;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using FileTime.Core.Command;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FileTime.Avalonia
|
||||
@@ -29,6 +30,15 @@ namespace FileTime.Avalonia
|
||||
throw new System.Exception("TODO: implement linux contextmenu provider");
|
||||
}
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
internal static IServiceCollection RegisterCommandHandlers(this IServiceCollection serviceCollection)
|
||||
{
|
||||
foreach (var commandHandler in FileTime.Providers.Local.Startup.GetCommandHandlers())
|
||||
{
|
||||
serviceCollection.AddTransient(typeof(ICommandHandler), commandHandler);
|
||||
}
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using FileTime.Avalonia.Application;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -16,8 +17,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Inject(typeof(ItemNameConverterService))]
|
||||
public partial class ContainerViewModel : IItemViewModel
|
||||
{
|
||||
private bool isRefreshing;
|
||||
private bool isInitialized;
|
||||
private bool _isRefreshing;
|
||||
private bool _isInitialized;
|
||||
private INewItemProcessor _newItemProcessor;
|
||||
|
||||
[Property]
|
||||
private IContainer _container;
|
||||
@@ -25,29 +27,39 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private bool _isSelected;
|
||||
|
||||
public IItem Item => _container;
|
||||
|
||||
//[Property]
|
||||
private readonly ObservableCollection<ContainerViewModel> _containers = new ObservableCollection<ContainerViewModel>();
|
||||
|
||||
//[Property]
|
||||
private readonly ObservableCollection<ElementViewModel> _elements = new ObservableCollection<ElementViewModel>();
|
||||
|
||||
//[Property]
|
||||
private readonly ObservableCollection<IItemViewModel> _items = new ObservableCollection<IItemViewModel>();
|
||||
|
||||
[Property]
|
||||
private bool _isAlternative;
|
||||
|
||||
[Property]
|
||||
private bool _isMarked;
|
||||
|
||||
[Property]
|
||||
private ContainerViewModel? _parent;
|
||||
|
||||
public IItem Item => _container;
|
||||
|
||||
private readonly ObservableCollection<ContainerViewModel> _containers = new ObservableCollection<ContainerViewModel>();
|
||||
|
||||
private readonly ObservableCollection<ElementViewModel> _elements = new ObservableCollection<ElementViewModel>();
|
||||
|
||||
private readonly ObservableCollection<IItemViewModel> _items = new ObservableCollection<IItemViewModel>();
|
||||
|
||||
public List<IItemViewModel> ChildrenToAdopt { get; } = new List<IItemViewModel>();
|
||||
|
||||
|
||||
[PropertyInvalidate(nameof(IsSelected))]
|
||||
[PropertyInvalidate(nameof(IsAlternative))]
|
||||
[PropertyInvalidate(nameof(IsMarked))]
|
||||
public ItemViewMode ViewMode =>
|
||||
IsSelected
|
||||
? ItemViewMode.Selected
|
||||
: IsAlternative
|
||||
? ItemViewMode.Alternative
|
||||
: ItemViewMode.Default;
|
||||
(IsMarked, IsSelected, IsAlternative) switch
|
||||
{
|
||||
(true, true, _) => ItemViewMode.MarkedSelected,
|
||||
(true, false, true) => ItemViewMode.MarkedAlternative,
|
||||
(false, true, _) => ItemViewMode.Selected,
|
||||
(false, false, true) => ItemViewMode.Alternative,
|
||||
(true, false, false) => ItemViewMode.Marked,
|
||||
_ => ItemViewMode.Default
|
||||
};
|
||||
|
||||
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
|
||||
|
||||
@@ -56,7 +68,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!isInitialized) Task.Run(Refresh);
|
||||
if (!_isInitialized) Task.Run(Refresh);
|
||||
return _containers;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +78,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!isInitialized) Task.Run(Refresh);
|
||||
if (!_isInitialized) Task.Run(Refresh);
|
||||
return _elements;
|
||||
}
|
||||
}
|
||||
@@ -76,13 +88,16 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!isInitialized) Task.Run(Refresh);
|
||||
if (!_isInitialized) Task.Run(Refresh);
|
||||
return _items;
|
||||
}
|
||||
}
|
||||
|
||||
public ContainerViewModel(IContainer container, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
||||
public ContainerViewModel(INewItemProcessor newItemProcessor, ContainerViewModel? parent, IContainer container, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
||||
{
|
||||
_newItemProcessor = newItemProcessor;
|
||||
Parent = parent;
|
||||
|
||||
Container = container;
|
||||
Container.Refreshed.Add(Container_Refreshed);
|
||||
}
|
||||
@@ -94,7 +109,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
await Refresh(initializeChildren);
|
||||
}
|
||||
|
||||
private async Task Container_Refreshed(object sender, AsyncEventArgs e)
|
||||
private async Task Container_Refreshed(object? sender, AsyncEventArgs e)
|
||||
{
|
||||
await Refresh(false);
|
||||
}
|
||||
@@ -105,16 +120,16 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
private async Task Refresh(bool initializeChildren)
|
||||
{
|
||||
if (isRefreshing) return;
|
||||
if (_isRefreshing) return;
|
||||
|
||||
isInitialized = true;
|
||||
_isInitialized = true;
|
||||
|
||||
try
|
||||
{
|
||||
isRefreshing = true;
|
||||
_isRefreshing = true;
|
||||
|
||||
var containers = (await _container.GetContainers()).Select(c => new ContainerViewModel(c, ItemNameConverterService)).ToList();
|
||||
var elements = (await _container.GetElements()).Select(e => new ElementViewModel(e, ItemNameConverterService)).ToList();
|
||||
var containers = (await _container.GetContainers()).Select(c => AdoptOrCreateItem(c, (c2) => new ContainerViewModel(_newItemProcessor, this, c2, ItemNameConverterService))).ToList();
|
||||
var elements = (await _container.GetElements()).Select(e => AdoptOrCreateItem(e, (e2) => new ElementViewModel(e2, this, ItemNameConverterService))).ToList();
|
||||
|
||||
_containers.Clear();
|
||||
_elements.Clear();
|
||||
@@ -141,24 +156,51 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
catch { }
|
||||
|
||||
isRefreshing = false;
|
||||
await _newItemProcessor.UpdateMarkedItems(this);
|
||||
|
||||
_isRefreshing = false;
|
||||
}
|
||||
|
||||
private TResult AdoptOrCreateItem<T, TResult>(T item, Func<T, TResult> generator) where T : IItem
|
||||
{
|
||||
var itemToAdopt = ChildrenToAdopt.Find(i => i.Item.Name == item.Name);
|
||||
if (itemToAdopt is TResult itemViewModel) return itemViewModel;
|
||||
|
||||
return generator(item);
|
||||
}
|
||||
|
||||
public void Unload(bool recursive = true)
|
||||
{
|
||||
_isInitialized = false;
|
||||
if (recursive)
|
||||
{
|
||||
foreach (var container in _containers)
|
||||
{
|
||||
container.Unload(true);
|
||||
container.ChildrenToAdopt.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
_containers.Clear();
|
||||
_elements.Clear();
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<ContainerViewModel>> GetContainers()
|
||||
{
|
||||
if (!isInitialized) await Task.Run(Refresh);
|
||||
if (!_isInitialized) await Task.Run(Refresh);
|
||||
return _containers;
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<ElementViewModel>> GetElements()
|
||||
{
|
||||
if (!isInitialized) await Task.Run(Refresh);
|
||||
if (!_isInitialized) await Task.Run(Refresh);
|
||||
return _elements;
|
||||
}
|
||||
|
||||
public async Task<ObservableCollection<IItemViewModel>> GetItems()
|
||||
{
|
||||
if (!isInitialized) await Task.Run(Refresh);
|
||||
if (!_isInitialized) await Task.Run(Refresh);
|
||||
return _items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,20 +21,32 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private bool _isAlternative;
|
||||
|
||||
[Property]
|
||||
private bool _isMarked;
|
||||
|
||||
[Property]
|
||||
private ContainerViewModel? _parent;
|
||||
|
||||
[PropertyInvalidate(nameof(IsSelected))]
|
||||
[PropertyInvalidate(nameof(IsAlternative))]
|
||||
[PropertyInvalidate(nameof(IsMarked))]
|
||||
public ItemViewMode ViewMode =>
|
||||
IsSelected
|
||||
? ItemViewMode.Selected
|
||||
: IsAlternative
|
||||
? ItemViewMode.Alternative
|
||||
: ItemViewMode.Default;
|
||||
(IsMarked, IsSelected, IsAlternative) switch
|
||||
{
|
||||
(true, true, _) => ItemViewMode.MarkedSelected,
|
||||
(true, false, true) => ItemViewMode.MarkedAlternative,
|
||||
(false, true, _) => ItemViewMode.Selected,
|
||||
(false, false, true) => ItemViewMode.Alternative,
|
||||
(true, false, false) => ItemViewMode.Marked,
|
||||
_ => ItemViewMode.Default
|
||||
};
|
||||
|
||||
public List<ItemNamePart> DisplayName => ItemNameConverterService.GetDisplayName(this);
|
||||
|
||||
public ElementViewModel(IElement element, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
||||
public ElementViewModel(IElement element, ContainerViewModel parent, ItemNameConverterService itemNameConverterService) : this(itemNameConverterService)
|
||||
{
|
||||
Element = element;
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
public void InvalidateDisplayName() => OnPropertyChanged(nameof(DisplayName));
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Avalonia.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -12,6 +10,8 @@ namespace FileTime.Avalonia.ViewModels
|
||||
bool IsSelected { get; set; }
|
||||
|
||||
bool IsAlternative { get; set; }
|
||||
bool IsMarked { get; set; }
|
||||
ContainerViewModel? Parent{ get; set; }
|
||||
|
||||
ItemViewMode ViewMode { get; }
|
||||
|
||||
|
||||
@@ -6,8 +6,11 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
public enum ItemViewMode
|
||||
{
|
||||
Selected,
|
||||
Default,
|
||||
Alternative,
|
||||
Default
|
||||
Selected,
|
||||
Marked,
|
||||
MarkedSelected,
|
||||
MarkedAlternative
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Input;
|
||||
using FileTime.App.Core.Clipboard;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using FileTime.Core.Command;
|
||||
using FileTime.Core.Timeline;
|
||||
|
||||
namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
@@ -34,6 +38,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
private List<CommandBinding> _commandBindings = new();
|
||||
private List<CommandBinding> _universalCommandBindings = new();
|
||||
|
||||
private IClipboard _clipboard;
|
||||
private TimeRunner _timeRunner;
|
||||
|
||||
private Action? _inputHandler;
|
||||
|
||||
[Property]
|
||||
@@ -51,10 +58,16 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private List<RootDriveInfo> _rootDriveInfos;
|
||||
|
||||
public Action? FocusDefaultElement { get; set; }
|
||||
[Property]
|
||||
private string _messageBoxText;
|
||||
|
||||
public IReadOnlyList<ReadOnlyParallelCommands> TimelineCommands => _timeRunner.ParallelCommands;
|
||||
|
||||
async partial void OnInitialize()
|
||||
{
|
||||
_clipboard = App.ServiceProvider.GetService<IClipboard>()!;
|
||||
_timeRunner = App.ServiceProvider.GetService<TimeRunner>()!;
|
||||
_timeRunner.CommandsChanged += (o, e) => OnPropertyChanged(nameof(TimelineCommands));
|
||||
InitCommandBindings();
|
||||
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.Up) });
|
||||
@@ -62,6 +75,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.Tab) });
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.PageDown) });
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.PageUp) });
|
||||
_keysToSkip.Add(new KeyWithModifiers[] { new KeyWithModifiers(Key.F4, alt: true) });
|
||||
|
||||
var tab = new Tab();
|
||||
await tab.Init(LocalContentProvider);
|
||||
@@ -176,7 +190,6 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
_previousKeys.Clear();
|
||||
PossibleCommands = new();
|
||||
FocusDefaultElement?.Invoke();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -190,7 +203,6 @@ namespace FileTime.Avalonia.ViewModels
|
||||
AppState.RapidTravelText = "";
|
||||
|
||||
await AppState.SelectedTab.OpenContainer(await AppState.SelectedTab.CurrentLocation.Container.WithoutVirtualContainer(RAPIDTRAVEL));
|
||||
FocusDefaultElement?.Invoke();
|
||||
}
|
||||
|
||||
public async Task SwitchToTab(int number)
|
||||
@@ -260,7 +272,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
if (Inputs != null)
|
||||
{
|
||||
AppState.SelectedTab.CreateContainer(Inputs[0].Value).Wait();
|
||||
var container = AppState.SelectedTab.CurrentLocation.Container;
|
||||
var createContainerCommand = new CreateContainerCommand(new Core.Models.AbsolutePath(container), Inputs[0].Value);
|
||||
_timeRunner.AddCommand(createContainerCommand).Wait();
|
||||
Inputs = null;
|
||||
}
|
||||
};
|
||||
@@ -270,10 +284,222 @@ namespace FileTime.Avalonia.ViewModels
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task CreateElement()
|
||||
{
|
||||
var handler = () =>
|
||||
{
|
||||
if (Inputs != null)
|
||||
{
|
||||
var container = AppState.SelectedTab.CurrentLocation.Container;
|
||||
var createElementCommand = new CreateElementCommand(new Core.Models.AbsolutePath(container), Inputs[0].Value);
|
||||
_timeRunner.AddCommand(createElementCommand).Wait();
|
||||
Inputs = null;
|
||||
}
|
||||
};
|
||||
|
||||
ReadInputs(new List<Core.Interactions.InputElement>() { new Core.Interactions.InputElement("Element name", InputType.Text) }, handler);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task MarkCurrentItem()
|
||||
{
|
||||
await AppState.SelectedTab.MarkCurrentItem();
|
||||
}
|
||||
|
||||
public async Task Copy()
|
||||
{
|
||||
_clipboard.Clear();
|
||||
_clipboard.SetCommand<CopyCommand>();
|
||||
|
||||
var currentSelectedItems = await AppState.SelectedTab.TabState.GetCurrentMarkedItems();
|
||||
if (currentSelectedItems.Count > 0)
|
||||
{
|
||||
foreach (var selectedItem in currentSelectedItems)
|
||||
{
|
||||
_clipboard.AddContent(selectedItem);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentSelectedItem = AppState.SelectedTab.SelectedItem?.Item;
|
||||
if (currentSelectedItem != null)
|
||||
{
|
||||
_clipboard.AddContent(new AbsolutePath(currentSelectedItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task Cut()
|
||||
{
|
||||
_clipboard.Clear();
|
||||
_clipboard.SetCommand<MoveCommand>();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task Delete()
|
||||
{
|
||||
IList<Core.Models.AbsolutePath>? itemsToDelete = null;
|
||||
var askForDelete = false;
|
||||
var questionText = "";
|
||||
var shouldDelete = false;
|
||||
|
||||
var currentSelectedItems = await AppState.SelectedTab.TabState.GetCurrentMarkedItems();
|
||||
var currentSelectedItem = AppState.SelectedTab.SelectedItem?.Item;
|
||||
if (currentSelectedItems.Count > 0)
|
||||
{
|
||||
itemsToDelete = currentSelectedItems.Cast<Core.Models.AbsolutePath>().ToList();
|
||||
|
||||
//FIXME: check 'is Container'
|
||||
if (currentSelectedItems.Count == 1)
|
||||
{
|
||||
if ((await currentSelectedItems[0].Resolve()) is IContainer container
|
||||
&& (await container.GetItems())?.Count > 0)
|
||||
{
|
||||
askForDelete = true;
|
||||
questionText = $"The container '{container.Name}' is not empty. Proceed with delete?";
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldDelete = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
askForDelete = true;
|
||||
questionText = $"Are you sure you want to delete {itemsToDelete.Count} item?";
|
||||
}
|
||||
}
|
||||
else if (currentSelectedItem != null)
|
||||
{
|
||||
itemsToDelete = new List<Core.Models.AbsolutePath>()
|
||||
{
|
||||
new Core.Models.AbsolutePath(currentSelectedItem)
|
||||
};
|
||||
|
||||
if (currentSelectedItem is IContainer container && (await container.GetItems())?.Count > 0)
|
||||
{
|
||||
askForDelete = true;
|
||||
questionText = $"The container '{container.Name}' is not empty. Proceed with delete?";
|
||||
}
|
||||
else
|
||||
{
|
||||
shouldDelete = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemsToDelete?.Count > 0)
|
||||
{
|
||||
if (askForDelete)
|
||||
{
|
||||
ShowMessageBox(questionText, HandleDelete);
|
||||
}
|
||||
else if (shouldDelete)
|
||||
{
|
||||
HandleDelete();
|
||||
}
|
||||
}
|
||||
|
||||
void HandleDelete()
|
||||
{
|
||||
var deleteCommand = new DeleteCommand();
|
||||
|
||||
foreach (var itemToDelete in itemsToDelete!)
|
||||
{
|
||||
deleteCommand.ItemsToDelete.Add(itemToDelete);
|
||||
}
|
||||
|
||||
_timeRunner.AddCommand(deleteCommand).Wait();
|
||||
_clipboard.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PasteMerge()
|
||||
{
|
||||
await Paste(TransportMode.Merge);
|
||||
}
|
||||
public async Task PasteOverwrite()
|
||||
{
|
||||
await Paste(TransportMode.Overwrite);
|
||||
}
|
||||
|
||||
public async Task PasteSkip()
|
||||
{
|
||||
await Paste(TransportMode.Skip);
|
||||
}
|
||||
|
||||
private async Task Paste(TransportMode transportMode)
|
||||
{
|
||||
if (_clipboard.CommandType != null)
|
||||
{
|
||||
var command = (ITransportationCommand)Activator.CreateInstance(_clipboard.CommandType!)!;
|
||||
command.TransportMode = transportMode;
|
||||
|
||||
command.Sources.Clear();
|
||||
|
||||
foreach (var item in _clipboard.Content)
|
||||
{
|
||||
command.Sources.Add(item);
|
||||
}
|
||||
|
||||
var currentLocation = AppState.SelectedTab.CurrentLocation.Container;
|
||||
command.Target = currentLocation is VirtualContainer virtualContainer
|
||||
? virtualContainer.BaseContainer
|
||||
: currentLocation;
|
||||
|
||||
await _timeRunner.AddCommand(command);
|
||||
|
||||
_clipboard.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private Task Rename()
|
||||
{
|
||||
var selectedItem = AppState.SelectedTab.SelectedItem?.Item;
|
||||
if (selectedItem != null)
|
||||
{
|
||||
var handler = () =>
|
||||
{
|
||||
if (Inputs != null)
|
||||
{
|
||||
var renameCommand = new RenameCommand(new Core.Models.AbsolutePath(selectedItem), Inputs[0].Value);
|
||||
_timeRunner.AddCommand(renameCommand).Wait();
|
||||
}
|
||||
};
|
||||
|
||||
ReadInputs(new List<Core.Interactions.InputElement>() { new Core.Interactions.InputElement("New name", InputType.Text, selectedItem.Name) }, handler);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task RefreshCurrentLocation()
|
||||
{
|
||||
await AppState.SelectedTab.CurrentLocation.Container.Refresh();
|
||||
await AppState.SelectedTab.UpdateCurrentSelectedItem();
|
||||
}
|
||||
|
||||
private Task PauseTimeline()
|
||||
{
|
||||
_timeRunner.EnableRunning = false;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task ContinueTimeline()
|
||||
{
|
||||
_timeRunner.EnableRunning = true;
|
||||
await _timeRunner.TryStartCommandRunner();
|
||||
}
|
||||
|
||||
private async Task RefreshTimeline()
|
||||
{
|
||||
await _timeRunner.Refresh();
|
||||
}
|
||||
|
||||
[Command]
|
||||
public void ProcessInputs()
|
||||
{
|
||||
_inputHandler();
|
||||
_inputHandler?.Invoke();
|
||||
|
||||
Inputs = null;
|
||||
_inputHandler = null;
|
||||
@@ -286,8 +512,31 @@ namespace FileTime.Avalonia.ViewModels
|
||||
_inputHandler = null;
|
||||
}
|
||||
|
||||
[Command]
|
||||
public void ProcessMessageBoxCommand()
|
||||
{
|
||||
_inputHandler?.Invoke();
|
||||
|
||||
MessageBoxText = null;
|
||||
_inputHandler = null;
|
||||
}
|
||||
|
||||
[Command]
|
||||
public void CancelMessageBoxCommand()
|
||||
{
|
||||
MessageBoxText = null;
|
||||
_inputHandler = null;
|
||||
}
|
||||
|
||||
public async Task<bool> ProcessKeyDown(Key key, KeyModifiers keyModifiers)
|
||||
{
|
||||
if (key == Key.LeftAlt
|
||||
|| key == Key.RightAlt
|
||||
|| key == Key.LeftShift
|
||||
|| key == Key.RightShift
|
||||
|| key == Key.LeftCtrl
|
||||
|| key == Key.RightCtrl) return false;
|
||||
|
||||
NoCommandFound = false;
|
||||
|
||||
var isAltPressed = (keyModifiers & KeyModifiers.Alt) == KeyModifiers.Alt;
|
||||
@@ -312,13 +561,12 @@ namespace FileTime.Avalonia.ViewModels
|
||||
await selectedCommandBinding.InvokeAsync();
|
||||
_previousKeys.Clear();
|
||||
PossibleCommands = new();
|
||||
|
||||
FocusDefaultElement?.Invoke();
|
||||
}
|
||||
else if (_keysToSkip.Any(k => AreKeysEqual(k, _previousKeys)))
|
||||
{
|
||||
_previousKeys.Clear();
|
||||
PossibleCommands = new();
|
||||
return false;
|
||||
}
|
||||
else if (_previousKeys.Count == 2)
|
||||
{
|
||||
@@ -370,7 +618,11 @@ namespace FileTime.Avalonia.ViewModels
|
||||
if (selectedCommandBinding != null)
|
||||
{
|
||||
await selectedCommandBinding.InvokeAsync();
|
||||
FocusDefaultElement?.Invoke();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,8 +651,6 @@ namespace FileTime.Avalonia.ViewModels
|
||||
{
|
||||
await AppState.SelectedTab.MoveCursorToFirst();
|
||||
}
|
||||
|
||||
FocusDefaultElement?.Invoke();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -415,7 +665,13 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
private void ReadInputs(List<Core.Interactions.InputElement> inputs, Action inputHandler)
|
||||
{
|
||||
Inputs = inputs.Select(i => new InputElementWrapper(i)).ToList();
|
||||
Inputs = inputs.Select(i => new InputElementWrapper(i, i.DefaultValue)).ToList();
|
||||
_inputHandler = inputHandler;
|
||||
}
|
||||
|
||||
private void ShowMessageBox(string text, Action inputHandler)
|
||||
{
|
||||
MessageBoxText = text;
|
||||
_inputHandler = inputHandler;
|
||||
}
|
||||
|
||||
@@ -451,6 +707,11 @@ namespace FileTime.Avalonia.ViewModels
|
||||
FileTime.App.Core.Command.Commands.CreateContainer,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.C),new KeyWithModifiers(Key.C)},
|
||||
CreateContainer),
|
||||
new CommandBinding(
|
||||
"create element",
|
||||
FileTime.App.Core.Command.Commands.CreateElement,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.C),new KeyWithModifiers(Key.E)},
|
||||
CreateElement),
|
||||
new CommandBinding(
|
||||
"move to first",
|
||||
FileTime.App.Core.Command.Commands.MoveToTop,
|
||||
@@ -526,6 +787,66 @@ namespace FileTime.Avalonia.ViewModels
|
||||
FileTime.App.Core.Command.Commands.GoToHome,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.Q)},
|
||||
CloseTab),
|
||||
new CommandBinding(
|
||||
"select",
|
||||
FileTime.App.Core.Command.Commands.Select,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.Space)},
|
||||
MarkCurrentItem),
|
||||
new CommandBinding(
|
||||
"copy",
|
||||
FileTime.App.Core.Command.Commands.Copy,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.Y),new KeyWithModifiers(Key.Y)},
|
||||
Copy),
|
||||
new CommandBinding(
|
||||
"cut",
|
||||
FileTime.App.Core.Command.Commands.Cut,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.D),new KeyWithModifiers(Key.D)},
|
||||
Cut),
|
||||
new CommandBinding(
|
||||
"delete",
|
||||
FileTime.App.Core.Command.Commands.Delete,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.D),new KeyWithModifiers(Key.D, shift: true)},
|
||||
Delete),
|
||||
new CommandBinding(
|
||||
"paste merge",
|
||||
FileTime.App.Core.Command.Commands.PasteMerge,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.P),new KeyWithModifiers(Key.P)},
|
||||
PasteMerge),
|
||||
new CommandBinding(
|
||||
"paste (overwrite)",
|
||||
FileTime.App.Core.Command.Commands.PasteOverwrite,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.P),new KeyWithModifiers(Key.O)},
|
||||
PasteOverwrite),
|
||||
new CommandBinding(
|
||||
"paste (skip)",
|
||||
FileTime.App.Core.Command.Commands.PasteSkip,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.P),new KeyWithModifiers(Key.S)},
|
||||
PasteSkip),
|
||||
new CommandBinding(
|
||||
"rename",
|
||||
FileTime.App.Core.Command.Commands.Rename,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.C),new KeyWithModifiers(Key.W)},
|
||||
Rename),
|
||||
new CommandBinding(
|
||||
"timeline pause",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.T),new KeyWithModifiers(Key.P)},
|
||||
PauseTimeline),
|
||||
new CommandBinding(
|
||||
"timeline start",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.T),new KeyWithModifiers(Key.S)},
|
||||
ContinueTimeline),
|
||||
new CommandBinding(
|
||||
"refresh timeline",
|
||||
FileTime.App.Core.Command.Commands.Dummy,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.T),new KeyWithModifiers(Key.R)},
|
||||
RefreshTimeline),
|
||||
new CommandBinding(
|
||||
"refresh",
|
||||
FileTime.App.Core.Command.Commands.Refresh,
|
||||
new KeyWithModifiers[]{new KeyWithModifiers(Key.R)},
|
||||
RefreshCurrentLocation),
|
||||
};
|
||||
var universalCommandBindings = new List<CommandBinding>()
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
Height="18"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Source="{SvgImage /Assets/material/folder.svg}" />
|
||||
Source="{Binding Converter={StaticResource ItemToImageConverter}}" />
|
||||
|
||||
<ItemsControl
|
||||
Grid.Column="1"
|
||||
|
||||
@@ -89,8 +89,29 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="1" RowDefinitions="40,*,Auto">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
<Grid Grid.Column="2" RowDefinitions="Auto,40,*,Auto">
|
||||
<Grid>
|
||||
<ItemsControl Items="{Binding TimelineCommands}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ItemsControl Items="{Binding Commands}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding}"/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" ColumnDefinitions="*,Auto">
|
||||
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
@@ -98,7 +119,7 @@
|
||||
Text="{Binding AppState.SelectedTab.TabNumber,StringFormat=({0})}" />
|
||||
|
||||
<local:PathPresenter Margin="10,5,0,5" DataContext="{Binding AppState.SelectedTab.CurrentLocation.Container.FullName}"/>
|
||||
|
||||
|
||||
<TextBlock
|
||||
Margin="0,5,10,5"
|
||||
Text="{Binding AppState.SelectedTab.SelectedItem.Item.Name}" Foreground="{StaticResource AccentForegroundBrush}" />
|
||||
@@ -123,7 +144,7 @@
|
||||
|
||||
<local:PathPresenter Margin="5,0,0,0" DataContext="{Binding CurrentLocation.Container.FullName}"/>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
<Rectangle Fill="{DynamicResource ForegroundBrush}" Grid.Row="1" IsVisible="{Binding IsSelected}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
@@ -132,7 +153,7 @@
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Grid.Row="2"
|
||||
Margin="20,0,0,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -146,8 +167,8 @@
|
||||
<Grid>
|
||||
<ListBox
|
||||
Classes="ContentListView"
|
||||
Items="{Binding AppState.SelectedTab.Parent.Items}"
|
||||
SelectedItem="{Binding AppState.SelectedTab.CurrentLocation}">
|
||||
IsEnabled="False"
|
||||
Items="{Binding AppState.SelectedTab.Parent.Items}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:ItemView ShowAttributes="False"/>
|
||||
@@ -202,6 +223,7 @@
|
||||
<Grid Grid.Column="4">
|
||||
<ListBox
|
||||
Classes="ContentListView"
|
||||
IsEnabled="False"
|
||||
x:Name="ChildItems"
|
||||
Items="{Binding AppState.SelectedTab.ChildContainer.Items}"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||
@@ -223,9 +245,78 @@
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding Inputs, Converter={StaticResource IsNotNullConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<ItemsControl
|
||||
x:Name="InputList"
|
||||
Items="{Binding Inputs}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid MinWidth="400">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition MinWidth="200" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<TextBlock
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding InputElement.Text}" />
|
||||
<TextBox
|
||||
AttachedToVisualTree="InputText_AttachedToVisualTree"
|
||||
Grid.Column="1"
|
||||
GotFocus="InputText_GotFocus"
|
||||
LostFocus="InputText_LostFocus"
|
||||
KeyDown="InputText_KeyDown"
|
||||
Text="{Binding Value, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Command="{Binding ProcessInputsCommand}"
|
||||
Content="Ok" />
|
||||
<Button
|
||||
Command="{Binding CancelInputsCommand}"
|
||||
Content="Cancel" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding MessageBoxText, Converter={StaticResource IsNotNullConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Text="{Binding MessageBoxText}"/>
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Orientation="Horizontal">
|
||||
<Button
|
||||
Command="{Binding ProcessMessageBoxCommand}"
|
||||
Content="Yes" />
|
||||
<Button
|
||||
Command="{Binding CancelMessageBoxCommand}"
|
||||
Content="No" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2">
|
||||
<Grid Grid.Row="3">
|
||||
<Grid IsVisible="{Binding AppState.ViewMode, Converter={StaticResource EqualityConverter}, ConverterParameter=RapidTravel}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="1" />
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using FileTime.Avalonia.Misc;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using System.Linq;
|
||||
|
||||
namespace FileTime.Avalonia.Views
|
||||
{
|
||||
@@ -15,21 +18,13 @@ namespace FileTime.Avalonia.Views
|
||||
{
|
||||
if (value != DataContext)
|
||||
{
|
||||
if (DataContext is MainPageViewModel currentViewModel)
|
||||
{
|
||||
currentViewModel.FocusDefaultElement = null;
|
||||
}
|
||||
|
||||
DataContext = value;
|
||||
|
||||
if (value != null)
|
||||
{
|
||||
//value.FocusDefaultElement = () => this.FindControl<ListBox>("CurrentItems")?.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InputElementWrapper? _inputElementWrapper;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -45,12 +40,58 @@ namespace FileTime.Avalonia.Views
|
||||
|
||||
public async void OnKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
await ViewModel?.ProcessKeyDown(e.Key, e.KeyModifiers);
|
||||
if (_inputElementWrapper == null)
|
||||
{
|
||||
e.Handled = e.Handled || await ViewModel?.ProcessKeyDown(e.Key, e.KeyModifiers);
|
||||
}
|
||||
}
|
||||
|
||||
public async void OnKeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
await ViewModel?.ProcessKeyUp(e.Key, e.KeyModifiers);
|
||||
if (_inputElementWrapper == null)
|
||||
{
|
||||
e.Handled = e.Handled || await ViewModel?.ProcessKeyUp(e.Key, e.KeyModifiers);
|
||||
}
|
||||
}
|
||||
|
||||
private void InputText_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter && _inputElementWrapper == ViewModel!.Inputs.Last())
|
||||
{
|
||||
ViewModel.ProcessInputs();
|
||||
_inputElementWrapper = null;
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (e.Key == Key.Escape && _inputElementWrapper == ViewModel!.Inputs.Last())
|
||||
{
|
||||
ViewModel.CancelInputs();
|
||||
_inputElementWrapper = null;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void InputText_GotFocus(object sender, GotFocusEventArgs e)
|
||||
{
|
||||
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper)
|
||||
{
|
||||
_inputElementWrapper = inputElementWrapper;
|
||||
}
|
||||
}
|
||||
|
||||
private void InputText_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper)
|
||||
{
|
||||
_inputElementWrapper = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void InputText_AttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs args)
|
||||
{
|
||||
if (sender is TextBox inputText && inputText.DataContext is InputElementWrapper inputElementWrapper && inputElementWrapper == ViewModel!.Inputs.First())
|
||||
{
|
||||
inputText.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user