Refactor, Design, ContextMenu
This commit is contained in:
@@ -2,57 +2,10 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:FileTime.Avalonia"
|
||||
xmlns:converters="using:FileTime.Avalonia.Converters"
|
||||
xmlns:views="using:FileTime.Avalonia.Views"
|
||||
x:Class="FileTime.Avalonia.App">
|
||||
<Application.DataTemplates>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Light"/>
|
||||
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Foreground" Value="{DynamicResource ForegroundBrush}"/>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.SmallText">
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox.ContentListView">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="ItemTemplate">
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<Grid ColumnDefinitions="Auto,*">
|
||||
<Image
|
||||
Grid.RowSpan="2"
|
||||
Width="20"
|
||||
Height="20"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Source="{SvgImage /Assets/material/folder.svg}" />
|
||||
|
||||
<ItemsControl
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Items="{Binding DisplayName}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<TextBlock
|
||||
Text="{Binding Text}" />
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
<Application.DataTemplates>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
@@ -64,6 +17,8 @@
|
||||
<Color x:Key="SelectedItemBackgroundColor">#93a1a1</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="SelectedItemForegroundColor">#073642</Color>
|
||||
|
||||
@@ -74,9 +29,9 @@
|
||||
<GradientStop Offset="0.5" Color="#93a1a1" />
|
||||
<GradientStop Offset="1" Color="#0093a1a1" />
|
||||
</LinearGradientBrush>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<SolidColorBrush
|
||||
x:Key="AppBackgroundBrush"
|
||||
Color="{DynamicResource AppBackgroundColor}" />
|
||||
@@ -97,6 +52,12 @@
|
||||
<SolidColorBrush
|
||||
x:Key="ForegroundBrush"
|
||||
Color="{DynamicResource ForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="AccentForegroundBrush"
|
||||
Color="{DynamicResource AccentForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="LightForegroundBrush"
|
||||
Color="{DynamicResource LightForegroundColor}" />
|
||||
<SolidColorBrush
|
||||
x:Key="AlternativeForegroundBrush"
|
||||
Color="{DynamicResource AlternativeForegroundColor}" />
|
||||
@@ -108,11 +69,48 @@
|
||||
x:Key="ErrorBrush"
|
||||
Color="{DynamicResource ErrorColor}" />
|
||||
|
||||
|
||||
<SolidColorBrush x:Key="SystemControlHighlightListAccentLowBrush" Color="{DynamicResource SelectedItemBackgroundColor}" />
|
||||
|
||||
<converters:FormatSizeConverter x:Key="FormatSizeConverter"/>
|
||||
<converters:CompareConverter x:Key="EqualityConverter"/>
|
||||
<converters:CompareConverter x:Key="NotEqualsConverter" ComparisonCondition="{x:Static converters:ComparisonCondition.NotEqual}"/>
|
||||
|
||||
<converters:SplitStringConverter x:Key="SplitStringConverter" />
|
||||
<converters:ItemViewModeToBrushConverter
|
||||
x:Key="ItemViewModeToForegroundConverter"
|
||||
DefaultBrush="{StaticResource ForegroundBrush}"
|
||||
AlternativeBrush="{StaticResource AlternativeForegroundBrush}"
|
||||
SelectedBrush="{StaticResource SelectedItemForegroundBrush}"/>
|
||||
<converters:ItemViewModeToBrushConverter
|
||||
x:Key="ItemViewModeToBackgroundConverter"
|
||||
DefaultBrush="{StaticResource ItemBackgroundBrush}"
|
||||
AlternativeBrush="{StaticResource AlternativeItemBackgroundBrush}"
|
||||
SelectedBrush="{StaticResource SelectedItemBackgroundBrush}"/>
|
||||
<converters:ContextMenuGenerator x:Key="ContextMenuGenerator"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Light"/>
|
||||
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Foreground" Value="{DynamicResource ForegroundBrush}"/>
|
||||
<Setter Property="FontSize" Value="16"/>
|
||||
</Style>
|
||||
<Style Selector="TextBlock.SmallText">
|
||||
<Setter Property="FontSize" Value="16"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox.ContentListView">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
</Style>
|
||||
<Style Selector="ListBox.ContentListView > ListBoxItem">
|
||||
<Setter Property="Margin" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="ContextMenu">
|
||||
<ContextMenu Items="{Binding Converter={StaticResource ContextMenuGenerator}}"/>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Application.Styles>
|
||||
</Application>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
|
||||
namespace FileTime.Avalonia.Application
|
||||
@@ -10,8 +11,7 @@ namespace FileTime.Avalonia.Application
|
||||
public partial class AppState
|
||||
{
|
||||
[Property]
|
||||
[PropertyCallMethod(nameof(TabsChanged))]
|
||||
private List<TabContainer> _tabs = new List<TabContainer>();
|
||||
private ObservableCollection<TabContainer> _tabs = new ObservableCollection<TabContainer>();
|
||||
|
||||
[Property]
|
||||
private TabContainer _selectedTab;
|
||||
@@ -22,9 +22,9 @@ namespace FileTime.Avalonia.Application
|
||||
[Property]
|
||||
private string _rapidTravelText = "";
|
||||
|
||||
private void TabsChanged()
|
||||
partial void OnInitialize()
|
||||
{
|
||||
SelectedTab ??= Tabs[0];
|
||||
_tabs.CollectionChanged += (o, e) => SelectedTab ??= Tabs.Count > 0 ? Tabs[0] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,12 @@ namespace FileTime.Avalonia.Application
|
||||
[Property]
|
||||
private ContainerViewModel _childContainer;
|
||||
|
||||
[Property]
|
||||
private int _tabNumber;
|
||||
|
||||
[Property]
|
||||
private bool _isSelected;
|
||||
|
||||
private IItemViewModel? _selectedItem;
|
||||
|
||||
public IItemViewModel? SelectedItem
|
||||
@@ -36,7 +42,7 @@ namespace FileTime.Avalonia.Application
|
||||
get => _selectedItem;
|
||||
set
|
||||
{
|
||||
if (_selectedItem != value && value != null)
|
||||
if (_selectedItem != value)// && value != null
|
||||
{
|
||||
_selectedItem = value;
|
||||
OnPropertyChanged("SelectedItem");
|
||||
@@ -45,8 +51,9 @@ namespace FileTime.Avalonia.Application
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
public async Task Init(int tabNumber)
|
||||
{
|
||||
TabNumber = tabNumber;
|
||||
Tab.CurrentLocationChanged.Add(Tab_CurrentLocationChanged);
|
||||
Tab.CurrentSelectedItemChanged.Add(Tab_CurrentSelectedItemChanged);
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data.Converters;
|
||||
using FileTime.Avalonia.Services;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class ContextMenuGenerator : IValueConverter
|
||||
{
|
||||
private readonly IContextMenuProvider _contextMenuProvider;
|
||||
|
||||
public ContextMenuGenerator()
|
||||
{
|
||||
_contextMenuProvider = App.ServiceProvider.GetService<IContextMenuProvider>() ?? throw new Exception($"No {nameof(IContextMenuProvider)} is registered.");
|
||||
}
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is ContainerViewModel containerViewModel)
|
||||
{
|
||||
return _contextMenuProvider.GetContextMenuForFolder(containerViewModel.Container);
|
||||
}
|
||||
|
||||
return new object[] { new MenuItem() { Header = "asd" } };
|
||||
}
|
||||
|
||||
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.Media;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class ItemViewModeToBrushConverter : IValueConverter
|
||||
{
|
||||
public Brush? DefaultBrush { get; set; }
|
||||
public Brush? AlternativeBrush { get; set; }
|
||||
public Brush? SelectedBrush { get; set; }
|
||||
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is ItemViewMode viewMode)
|
||||
{
|
||||
return viewMode switch
|
||||
{
|
||||
ItemViewMode.Default => DefaultBrush,
|
||||
ItemViewMode.Alternative => AlternativeBrush,
|
||||
ItemViewMode.Selected => SelectedBrush,
|
||||
_ => throw new NotImplementedException()
|
||||
};
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace FileTime.Avalonia.Converters
|
||||
{
|
||||
public class SplitStringConverter : IValueConverter
|
||||
{
|
||||
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is string path && parameter is string separator)
|
||||
{
|
||||
return path.Split(separator);
|
||||
}
|
||||
else if (value is string path2 && parameter is char separator2)
|
||||
{
|
||||
return path2.Split(separator2);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using FileTime.Core.Models;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FileTime.Avalonia.Services
|
||||
{
|
||||
public interface IContextMenuProvider
|
||||
{
|
||||
List<object> GetContextMenuForFolder(IContainer container);
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,7 @@ using FileTime.Avalonia.Application;
|
||||
using FileTime.Avalonia.Models;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using MvvmGen;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace FileTime.Avalonia.Services
|
||||
{
|
||||
@@ -35,8 +33,7 @@ namespace FileTime.Avalonia.Services
|
||||
nameParts.Add(new ItemNamePart(before));
|
||||
}
|
||||
|
||||
//TODO: underline
|
||||
nameParts.Add(new ItemNamePart(rapidTravel) { /*TextDecorations = new TextDecorationCollection() {new TextDecoration() }*/ });
|
||||
nameParts.Add(new ItemNamePart(rapidTravel) { TextDecorations = TextDecorations.Underline });
|
||||
}
|
||||
|
||||
if (nameLeft.Length > 0)
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
using System;
|
||||
using Microsoft.Win32;
|
||||
using Avalonia.Controls;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Providers.Local;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
|
||||
#pragma warning disable CA1416
|
||||
namespace FileTime.Avalonia.Services
|
||||
{
|
||||
public class WindowsContextMenuProvider : IContextMenuProvider
|
||||
{
|
||||
public List<object> GetContextMenuForFolder(IContainer container)
|
||||
{
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) throw new NotSupportedException();
|
||||
|
||||
var menuItems = new List<object>();
|
||||
|
||||
if (container is LocalFolder localFolder)
|
||||
{
|
||||
ProcessKey(Registry.ClassesRoot.OpenSubKey("Directory"), menuItems, localFolder.Directory.FullName);
|
||||
}
|
||||
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
private void ProcessKey(RegistryKey? contextMenuContainer, List<object> menuItems, string folderPath)
|
||||
{
|
||||
var shell = contextMenuContainer?.OpenSubKey("shell");
|
||||
if (shell == null) return;
|
||||
|
||||
var shellSubKeys = shell.GetSubKeyNames();
|
||||
|
||||
foreach (var shellKey in shellSubKeys.Select(k => shell.OpenSubKey(k)).OfType<RegistryKey>())
|
||||
{
|
||||
var textBase = shellKey.GetValue(null) as string ?? shellKey.GetValue("MUIVerb") as string;
|
||||
|
||||
if (textBase == null) continue;
|
||||
|
||||
string? text = null;
|
||||
if (textBase.StartsWith("@"))
|
||||
{
|
||||
var parts = textBase[1..].Split(',');
|
||||
if (parts.Length == 2 && long.TryParse(parts[1], out var parsedResourceId))
|
||||
{
|
||||
if (parsedResourceId < 0) parsedResourceId *= -1;
|
||||
|
||||
text = GetStringResource(parts[0], (uint)parsedResourceId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
text = textBase;
|
||||
}
|
||||
|
||||
if (text != null)
|
||||
{
|
||||
text = text.Replace("&", "");
|
||||
|
||||
if (shellKey.GetSubKeyNames().Contains("command") && shellKey.OpenSubKey("command")?.GetValue(null) is string commandString)
|
||||
{
|
||||
var item = new MenuItem() { Header = text };
|
||||
item.Click += (o, e) => MenuItemClick(folderPath, commandString);
|
||||
menuItems.Add(item);
|
||||
}
|
||||
else if (shellKey.GetValue("ExtendedSubCommandsKey") is string extendedCommands)
|
||||
{
|
||||
var rootMenu = new MenuItem() { Header = text };
|
||||
var rootMenuItems = new List<object>();
|
||||
|
||||
ProcessKey(Registry.ClassesRoot.OpenSubKey(extendedCommands), rootMenuItems, folderPath);
|
||||
|
||||
rootMenu.Items = rootMenuItems.ToArray();
|
||||
menuItems.Add(rootMenu);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void MenuItemClick(string folderPath, string commandString)
|
||||
{
|
||||
var commandPartsWithoutAp = commandString.Split('\"').ToList();
|
||||
var commandParts = new List<List<string>>();
|
||||
|
||||
for (var i = 0; i < commandPartsWithoutAp.Count; i++)
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
{
|
||||
commandParts.Add(commandPartsWithoutAp[i].Split(' ').ToList());
|
||||
}
|
||||
else
|
||||
{
|
||||
commandParts.Add(new List<string> { commandPartsWithoutAp[i] });
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < commandParts.Count; i++)
|
||||
{
|
||||
for (var i2 = 0; i2 < commandParts[i].Count; i2++)
|
||||
{
|
||||
/*var commandPart = commandParts[i][i2];
|
||||
|
||||
if (commandPart == "%1" || commandPart == "%V") commandParts[i][i2] = folderPath;*/
|
||||
|
||||
commandParts[i][i2] = commandParts[i][i2].Replace("%1", folderPath).Replace("%V", folderPath);
|
||||
}
|
||||
}
|
||||
|
||||
var commandPartsWithoutEmpty = commandParts.SelectMany(c => c).Where(c => !string.IsNullOrWhiteSpace(c)).ToList();
|
||||
|
||||
if (commandPartsWithoutEmpty.Count == 1)
|
||||
{
|
||||
Process.Start(commandPartsWithoutEmpty[0]);
|
||||
}
|
||||
else if (commandPartsWithoutEmpty.Count > 1)
|
||||
{
|
||||
var paramStartIndex1 = -1;
|
||||
var paramStartIndex2 = -1;
|
||||
var found = false;
|
||||
|
||||
for (var x = 0; x < commandParts.Count && paramStartIndex1 == -1; x++)
|
||||
{
|
||||
for (var y = 0; y < commandParts[x].Count; y++)
|
||||
{
|
||||
if (found)
|
||||
{
|
||||
paramStartIndex1 = x;
|
||||
paramStartIndex2 = y;
|
||||
break;
|
||||
}
|
||||
|
||||
if (commandParts[x][y] == commandPartsWithoutEmpty[0])
|
||||
{
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
var arguments = SumList(commandParts, paramStartIndex1, paramStartIndex2);
|
||||
|
||||
try
|
||||
{
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = commandPartsWithoutEmpty[0];
|
||||
process.StartInfo.Arguments = arguments;
|
||||
process.Start();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (commandParts[0].Count > 0)
|
||||
{
|
||||
var executable = "";
|
||||
var lastExecutablePart = 0;
|
||||
for (lastExecutablePart = 0; !File.Exists(executable) && lastExecutablePart < commandParts[0].Count; lastExecutablePart++)
|
||||
{
|
||||
executable += (lastExecutablePart == 0 ? "" : " ") + commandParts[0][lastExecutablePart];
|
||||
}
|
||||
|
||||
lastExecutablePart--;
|
||||
|
||||
if (File.Exists(executable))
|
||||
{
|
||||
try
|
||||
{
|
||||
var (paramStartX, paramStartY) = GetCoordinatesFrom(commandParts, 1, 0, lastExecutablePart);
|
||||
arguments = SumList(commandParts, paramStartX, paramStartY);
|
||||
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = executable;
|
||||
process.StartInfo.Arguments = arguments;
|
||||
process.Start();
|
||||
}
|
||||
catch
|
||||
{
|
||||
//TODO: error message
|
||||
}
|
||||
}
|
||||
}
|
||||
//TODO: ELSE error message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string SumList(List<List<string>> data, int paramStartIndex1, int paramStartIndex2)
|
||||
{
|
||||
var result = "";
|
||||
|
||||
for (var x = paramStartIndex1; x < data.Count; x++)
|
||||
{
|
||||
if (x % 2 == 1) result += "\"";
|
||||
|
||||
result += string.Join(
|
||||
' ',
|
||||
x == paramStartIndex1
|
||||
? data[x].Skip(paramStartIndex2)
|
||||
: data[x]
|
||||
);
|
||||
|
||||
if (x % 2 == 1) result += "\"";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static (int, int) GetCoordinatesFrom(List<List<string>> data, int skip, int startX = 0, int startY = 0)
|
||||
{
|
||||
int skipping = 0;
|
||||
var x = startX;
|
||||
var y = startY;
|
||||
|
||||
var resultX = -1;
|
||||
var resultY = -1;
|
||||
for (; x < data.Count && resultX == -1; x++, y = 0)
|
||||
{
|
||||
for (; y < data[x].Count; y++)
|
||||
{
|
||||
if (skipping == skip)
|
||||
{
|
||||
resultX = x;
|
||||
resultY = y;
|
||||
break;
|
||||
}
|
||||
|
||||
skipping++;
|
||||
}
|
||||
}
|
||||
|
||||
return (resultX, resultY);
|
||||
}
|
||||
|
||||
static string GetStringResource(string fileName, uint resourceId)
|
||||
{
|
||||
IntPtr? handle = null;
|
||||
try
|
||||
{
|
||||
handle = NativeMethods.LoadLibrary(fileName);
|
||||
StringBuilder buffer = new(8192); //Buffer for output from LoadString()
|
||||
int length = NativeMethods.LoadString(handle.Value, resourceId, buffer, buffer.Capacity);
|
||||
return buffer.ToString(0, length); //Return the part of the buffer that was used.
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (handle is IntPtr validHandle)
|
||||
{
|
||||
NativeMethods.FreeLibrary(validHandle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class NativeMethods
|
||||
{
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
internal static extern IntPtr LoadLibrary(string lpLibFileName);
|
||||
|
||||
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true, BestFitMapping = false, ThrowOnUnmappableChar = true)]
|
||||
internal static extern int LoadString(IntPtr hInstance, uint wID, StringBuilder lpBuffer, int nBufferMax);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
public static extern int FreeLibrary(IntPtr hLibModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1416
|
||||
@@ -1,4 +1,5 @@
|
||||
using FileTime.Avalonia.Application;
|
||||
using System.Runtime.InteropServices;
|
||||
using FileTime.Avalonia.Application;
|
||||
using FileTime.Avalonia.Services;
|
||||
using FileTime.Avalonia.ViewModels;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -15,9 +16,20 @@ namespace FileTime.Avalonia
|
||||
}
|
||||
internal static IServiceCollection AddServices(this IServiceCollection serviceCollection)
|
||||
{
|
||||
return serviceCollection
|
||||
serviceCollection = serviceCollection
|
||||
.AddLogging()
|
||||
.AddSingleton<ItemNameConverterService>();
|
||||
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
serviceCollection.AddSingleton<IContextMenuProvider, WindowsContextMenuProvider>();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.Exception("TODO: implement linux contextmenu provider");
|
||||
}
|
||||
|
||||
return serviceCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +51,6 @@ namespace FileTime.Avalonia.ViewModels
|
||||
[Property]
|
||||
private List<RootDriveInfo> _rootDriveInfos;
|
||||
|
||||
[Property]
|
||||
private ObservableCollection<TabControlViewModel> _tabs = new ObservableCollection<TabControlViewModel>();
|
||||
|
||||
public Action? FocusDefaultElement { get; set; }
|
||||
|
||||
async partial void OnInitialize()
|
||||
@@ -70,13 +67,9 @@ namespace FileTime.Avalonia.ViewModels
|
||||
await tab.Init(LocalContentProvider);
|
||||
|
||||
var tabContainer = new TabContainer(tab, LocalContentProvider, ItemNameConverterService);
|
||||
await tabContainer.Init();
|
||||
AppState.Tabs = new List<TabContainer>()
|
||||
{
|
||||
tabContainer
|
||||
};
|
||||
|
||||
_tabs.Add(new TabControlViewModel(1, tabContainer));
|
||||
await tabContainer.Init(1);
|
||||
tabContainer.IsSelected = true;
|
||||
AppState.Tabs.Add(tabContainer);
|
||||
|
||||
var driveInfos = new List<RootDriveInfo>();
|
||||
foreach (var drive in DriveInfo.GetDrives().Where(d => d.DriveType == DriveType.Fixed))
|
||||
@@ -91,8 +84,7 @@ namespace FileTime.Avalonia.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: order by
|
||||
RootDriveInfos = driveInfos;
|
||||
RootDriveInfos = driveInfos.OrderBy(d => d.Name).ToList();
|
||||
}
|
||||
|
||||
private async Task<IContainer> GetContainerForWindowsDrive(DriveInfo drive)
|
||||
@@ -203,30 +195,29 @@ namespace FileTime.Avalonia.ViewModels
|
||||
|
||||
public async Task SwitchToTab(int number)
|
||||
{
|
||||
var tab = _tabs.FirstOrDefault(t => t.TabNumber == number);
|
||||
var tabContainer = AppState.Tabs.FirstOrDefault(t => t.TabNumber == number);
|
||||
|
||||
if (number == -1)
|
||||
{
|
||||
var greatestNumber = _tabs.Select(t => t.TabNumber).Max();
|
||||
tab = _tabs.FirstOrDefault(t => t.TabNumber == greatestNumber);
|
||||
var greatestNumber = AppState.Tabs.Select(t => t.TabNumber).Max();
|
||||
tabContainer = AppState.Tabs.FirstOrDefault(t => t.TabNumber == greatestNumber);
|
||||
}
|
||||
else if (tab == null)
|
||||
else if (tabContainer == null)
|
||||
{
|
||||
var newContainer = await AppState.SelectedTab.CurrentLocation.Container.Clone();
|
||||
|
||||
var newTab = new Tab();
|
||||
await newTab.Init(newContainer);
|
||||
|
||||
var tabContainer = new TabContainer(newTab, LocalContentProvider, ItemNameConverterService);
|
||||
await tabContainer.Init();
|
||||
tabContainer = new TabContainer(newTab, LocalContentProvider, ItemNameConverterService);
|
||||
await tabContainer.Init(number);
|
||||
|
||||
tab = new TabControlViewModel(number, tabContainer);
|
||||
var i = 0;
|
||||
for (i = 0; i < Tabs.Count; i++)
|
||||
for (i = 0; i < AppState.Tabs.Count; i++)
|
||||
{
|
||||
if (Tabs[i].TabNumber > number) break;
|
||||
if (AppState.Tabs[i].TabNumber > number) break;
|
||||
}
|
||||
Tabs.Insert(i, tab);
|
||||
AppState.Tabs.Insert(i, tabContainer);
|
||||
}
|
||||
|
||||
if (AppState.ViewMode == ViewMode.RapidTravel)
|
||||
@@ -234,28 +225,29 @@ namespace FileTime.Avalonia.ViewModels
|
||||
await ExitRapidTravelMode();
|
||||
}
|
||||
|
||||
AppState.SelectedTab = tab.Tab;
|
||||
AppState.SelectedTab = tabContainer;
|
||||
|
||||
foreach (var tab2 in Tabs)
|
||||
foreach (var tab2 in AppState.Tabs)
|
||||
{
|
||||
tab2.IsSelected = tab2.TabNumber == tab.TabNumber;
|
||||
tab2.IsSelected = tab2.TabNumber == tabContainer!.TabNumber;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task CloseTab()
|
||||
{
|
||||
if (_tabs.Count > 1)
|
||||
var tabs = AppState.Tabs;
|
||||
if (tabs.Count > 1)
|
||||
{
|
||||
var currentTab = _tabs.FirstOrDefault(t => t.Tab == AppState.SelectedTab);
|
||||
var currentTab = tabs.FirstOrDefault(t => t == AppState.SelectedTab);
|
||||
|
||||
if (currentTab != null)
|
||||
{
|
||||
_tabs.Remove(currentTab);
|
||||
var tabNumber = _tabs[0].TabNumber;
|
||||
for (var i = 0; i < Tabs.Count; i++)
|
||||
tabs.Remove(currentTab);
|
||||
var tabNumber = tabs[0].TabNumber;
|
||||
for (var i = 0; i < tabs.Count; i++)
|
||||
{
|
||||
tabNumber = _tabs[i].TabNumber;
|
||||
if (Tabs[i].TabNumber > currentTab.TabNumber) break;
|
||||
tabNumber = tabs[i].TabNumber;
|
||||
if (tabs[i].TabNumber > currentTab.TabNumber) break;
|
||||
}
|
||||
await SwitchToTab(tabNumber);
|
||||
}
|
||||
|
||||
49
src/GuiApp/FileTime.Avalonia/Views/ItemView.axaml
Normal file
49
src/GuiApp/FileTime.Avalonia/Views/ItemView.axaml
Normal file
@@ -0,0 +1,49 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="FileTime.Avalonia.Views.ItemView"
|
||||
x:Name="ItemRoot" Background="{Binding ViewMode,Converter={StaticResource ItemViewModeToBackgroundConverter}}">
|
||||
<Grid ColumnDefinitions="Auto,*,Auto" Margin="3">
|
||||
<Grid.Styles>
|
||||
<Style Selector="TextBlock">
|
||||
<Setter Property="Foreground" Value="{Binding DataContext.ViewMode,Converter={StaticResource ItemViewModeToForegroundConverter},ElementName=ItemRoot}"/>
|
||||
</Style>
|
||||
</Grid.Styles>
|
||||
<Image
|
||||
Grid.RowSpan="2"
|
||||
Width="18"
|
||||
Height="18"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Source="{SvgImage /Assets/material/folder.svg}" />
|
||||
|
||||
<ItemsControl
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Items="{Binding DisplayName}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<TextBlock
|
||||
Text="{Binding Text}"
|
||||
TextDecorations="{Binding TextDecorations}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
|
||||
<Grid Grid.Column="2" IsVisible="{Binding ShowAttributes,ElementName=ItemRoot}">
|
||||
<Grid ColumnDefinitions="180,40">
|
||||
<TextBlock Text="{Binding Item.CreatedAt}"/>
|
||||
<TextBlock Grid.Column="1" Text="{Binding Item.Attributes}"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
27
src/GuiApp/FileTime.Avalonia/Views/ItemView.axaml.cs
Normal file
27
src/GuiApp/FileTime.Avalonia/Views/ItemView.axaml.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace FileTime.Avalonia.Views
|
||||
{
|
||||
public partial class ItemView : UserControl
|
||||
{
|
||||
public static readonly StyledProperty<bool> ShowAttributesProperty = AvaloniaProperty.Register<ItemView, bool>(nameof(ShowAttributes), true);
|
||||
|
||||
public bool ShowAttributes
|
||||
{
|
||||
get => GetValue(ShowAttributesProperty);
|
||||
set => SetValue(ShowAttributesProperty, value);
|
||||
}
|
||||
|
||||
public ItemView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="using:FileTime.Avalonia.ViewModels"
|
||||
xmlns:local="using:FileTime.Avalonia.Views"
|
||||
Title="FileTime.Avalonia"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
@@ -88,19 +89,25 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="1" RowDefinitions="Auto,*,Auto">
|
||||
<Grid Grid.Column="1" RowDefinitions="40,*,Auto">
|
||||
<Grid ColumnDefinitions="*,Auto">
|
||||
|
||||
<Grid>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock
|
||||
Margin="10,5"
|
||||
Text="{Binding AppState.SelectedTab.CurrentLocation.Container.FullName}" />
|
||||
</Grid>
|
||||
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}" />
|
||||
</StackPanel>
|
||||
|
||||
<ItemsControl
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Items="{Binding Tabs}">
|
||||
Items="{Binding AppState.Tabs}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
@@ -108,38 +115,17 @@
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal"
|
||||
Margin="10,0,0,0">
|
||||
<!--i:Interaction.Behaviors>
|
||||
<ic:DataTriggerBehavior
|
||||
Binding="{Binding IsSelected}"
|
||||
Value="True">
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Background"
|
||||
Value="{DynamicResource ForegroundBrush}" />
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Foreground"
|
||||
TargetObject="{Binding ElementName=Text}"
|
||||
Value="{DynamicResource ContainerBackgroundBrush}" />
|
||||
</ic:DataTriggerBehavior>
|
||||
<ic:DataTriggerBehavior
|
||||
Binding="{Binding IsSelected}"
|
||||
Value="False">
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Background"
|
||||
Value="{DynamicResource ContainerBackgroundBrush}" />
|
||||
<ic:ChangePropertyAction
|
||||
PropertyName="Foreground"
|
||||
TargetObject="{Binding ElementName=Text}"
|
||||
Value="{DynamicResource ForegroundBrush}" />
|
||||
</ic:DataTriggerBehavior>
|
||||
</i:Interaction.Behaviors-->
|
||||
<Grid RowDefinitions="Auto,1">
|
||||
<StackPanel Orientation="Horizontal" Margin="20,0,20,0">
|
||||
|
||||
<TextBlock
|
||||
VerticalAlignment="Center" Text="{Binding TabNumber}" />
|
||||
<TextBlock Margin="5,0,0,0"
|
||||
VerticalAlignment="Center" Text="{Binding Tab.CurrentLocation.Container.FullName}" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center" Text="{Binding TabNumber,StringFormat=({0})}" />
|
||||
|
||||
<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>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
@@ -147,7 +133,7 @@
|
||||
|
||||
<Grid
|
||||
Grid.Row="1"
|
||||
Margin="20">
|
||||
Margin="20,0,0,0">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="15*" />
|
||||
@@ -161,7 +147,13 @@
|
||||
<ListBox
|
||||
Classes="ContentListView"
|
||||
Items="{Binding AppState.SelectedTab.Parent.Items}"
|
||||
SelectedItem="{Binding AppState.SelectedTab.CurrentLocation}" />
|
||||
SelectedItem="{Binding AppState.SelectedTab.CurrentLocation}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:ItemView ShowAttributes="False"/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
</Grid>
|
||||
|
||||
<Rectangle
|
||||
@@ -180,13 +172,20 @@
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
||||
SelectedItem="{Binding AppState.SelectedTab.SelectedItem, Mode=TwoWay}"
|
||||
Classes="ContentListView" />
|
||||
Classes="ContentListView">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:ItemView/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock
|
||||
x:Name="CurrentEmpty"
|
||||
Margin="10"
|
||||
HorizontalAlignment="Center"
|
||||
FontWeight="Bold"
|
||||
Foreground="{DynamicResource ErrorBrush}"
|
||||
IsVisible="{Binding AppState.SelectedTab.CurrentLocation.Items.Count, Converter={StaticResource EqualityConverter}, ConverterParameter=0}">
|
||||
Empty
|
||||
</TextBlock>
|
||||
@@ -205,7 +204,13 @@
|
||||
Classes="ContentListView"
|
||||
x:Name="ChildItems"
|
||||
Items="{Binding AppState.SelectedTab.ChildContainer.Items}"
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}" />
|
||||
IsVisible="{Binding AppState.SelectedTab.ChildContainer.Items.Count, Converter={StaticResource NotEqualsConverter}, ConverterParameter=0}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<local:ItemView/>
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</ListBox>
|
||||
|
||||
<TextBlock
|
||||
x:Name="ChildEmpty"
|
||||
|
||||
23
src/GuiApp/FileTime.Avalonia/Views/PathPresenter.axaml
Normal file
23
src/GuiApp/FileTime.Avalonia/Views/PathPresenter.axaml
Normal file
@@ -0,0 +1,23 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:coremodels="using:FileTime.Core.Models"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="FileTime.Avalonia.Views.PathPresenter">
|
||||
<ItemsControl Items="{Binding Converter={StaticResource SplitStringConverter}, ConverterParameter={x:Static coremodels:Constants.SeparatorChar}}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="{Binding}"/>
|
||||
<TextBlock Text="/" Margin="5,0,5,0" Foreground="{DynamicResource LightForegroundBrush}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</UserControl>
|
||||
19
src/GuiApp/FileTime.Avalonia/Views/PathPresenter.axaml.cs
Normal file
19
src/GuiApp/FileTime.Avalonia/Views/PathPresenter.axaml.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace FileTime.Avalonia.Views
|
||||
{
|
||||
public partial class PathPresenter : UserControl
|
||||
{
|
||||
public PathPresenter()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,9 +8,7 @@ namespace FileTime.Providers.Local
|
||||
{
|
||||
public class LocalFile : IElement
|
||||
{
|
||||
private readonly FileInfo _file;
|
||||
|
||||
public FileInfo File => _file;
|
||||
public FileInfo File { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
@@ -18,26 +16,46 @@ namespace FileTime.Providers.Local
|
||||
|
||||
public IContentProvider Provider { get; }
|
||||
|
||||
public bool IsHidden => (_file.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||
public bool IsHidden => (File.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
|
||||
public bool IsSpecial =>
|
||||
RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|
||||
&& (new UnixFileInfo(_file.FullName).FileAccessPermissions & FileAccessPermissions.UserExecute) == FileAccessPermissions.UserExecute;
|
||||
&& (new UnixFileInfo(File.FullName).FileAccessPermissions & FileAccessPermissions.UserExecute) == FileAccessPermissions.UserExecute;
|
||||
|
||||
public string Attributes => GetAttributes();
|
||||
|
||||
public DateTime CreatedAt => File.CreationTime;
|
||||
|
||||
public LocalFile(FileInfo file, IContentProvider contentProvider)
|
||||
{
|
||||
_file = file;
|
||||
File = file;
|
||||
|
||||
Name = file.Name;
|
||||
FullName = file.FullName;
|
||||
Provider = contentProvider;
|
||||
}
|
||||
|
||||
public string GetPrimaryAttributeText() => _file.Length.ToSizeString();
|
||||
public string GetPrimaryAttributeText() => File.Length.ToSizeString();
|
||||
|
||||
public Task Delete()
|
||||
{
|
||||
_file.Delete();
|
||||
File.Delete();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string GetAttributes()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "-"
|
||||
+ ((File.Attributes & FileAttributes.Archive) == FileAttributes.Archive ? "a" : "-")
|
||||
+ ((File.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly ? "r" : "-")
|
||||
+ ((File.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden ? "h" : "-")
|
||||
+ ((File.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using AsyncEvent;
|
||||
using FileTime.Core.Models;
|
||||
using FileTime.Core.Providers;
|
||||
@@ -21,6 +22,9 @@ namespace FileTime.Providers.Local
|
||||
public string FullName { get; }
|
||||
|
||||
public AsyncEventHandler Refreshed { get; } = new();
|
||||
public string Attributes => GetAttributes();
|
||||
|
||||
public DateTime CreatedAt => Directory.CreationTime;
|
||||
|
||||
public LocalFolder(DirectoryInfo directory, LocalContentProvider contentProvider, IContainer? parent)
|
||||
{
|
||||
@@ -111,5 +115,21 @@ namespace FileTime.Providers.Local
|
||||
Directory.Delete(true);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string GetAttributes()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "d"
|
||||
+ ((Directory.Attributes & FileAttributes.Archive) == FileAttributes.Archive ? "a" : "-")
|
||||
+ ((Directory.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly ? "r" : "-")
|
||||
+ ((Directory.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden ? "h" : "-")
|
||||
+ ((Directory.Attributes & FileAttributes.System) == FileAttributes.System ? "s" : "-");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user