diff --git a/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs b/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs index 96891f7..cb77547 100644 --- a/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs +++ b/src/AppCommon/FileTime.App.Core/ViewModels/ItemPreview/ElementPreviewViewModel.cs @@ -9,11 +9,23 @@ namespace FileTime.App.Core.ViewModels.ItemPreview; [ViewModel] public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInitable { + private record EncodingResult(char BinaryChar, string PartialResult); + private const int MaxTextPreviewSize = 1024 * 1024; + private static readonly List _encodings = new() + { + Encoding.UTF8, + Encoding.Unicode, + Encoding.ASCII, + Encoding.UTF32, + Encoding.BigEndianUnicode + }; + public ItemPreviewMode Mode { get; private set; } [Property] private string? _textContent; + [Property] private string? _textEncoding; public async Task InitAsync(IElement element) { @@ -21,9 +33,17 @@ public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInit { var content = await element.Provider.GetContentAsync(element, MaxTextPreviewSize); - TextContent = content is null - ? "Could not read any data from file " + element.Name - : GetNormalizedText(Encoding.UTF8.GetString(content)); + if (content is null) + { + TextContent = "Could not read any data from file " + element.Name; + } + else + { + (TextContent, var encoding) = GetNormalizedText(content); + TextEncoding = encoding is null + ? null + : $"{encoding.EncodingName} ({encoding.WebName})"; + } } catch (Exception ex) { @@ -36,14 +56,58 @@ public partial class ElementPreviewViewModel : IItemPreviewViewModel, IAsyncInit _ => ItemPreviewMode.Text }; - string GetNormalizedText(string text) + (string, Encoding?) GetNormalizedText(byte[] data) { - foreach (var c in text) + var binaryCharacter = new Dictionary(); + foreach (var encoding in _encodings) { - if (c < 32 && c != 9 && c != 10 && c != 13) return $"Binary data, contains '{(int) c}'"; + var text = encoding.GetString(data); + var binary = false; + for (var i = 0; i < text.Length; i++) + { + var c = text[i]; + if (c < 32 && c != 9 && c != 10 && c != 13) + { + binaryCharacter[encoding.EncodingName] = + new EncodingResult( + c, + i == 0 + ? string.Empty + : text.Substring(0, i - 1) + ); + + binary = true; + break; + } + } + + if (binary) continue; + + return (text, encoding); } - return text; + var stringBuilder = new StringBuilder(); + stringBuilder.AppendLine("The following binary characters were found by encodings:"); + foreach (var binaryByEncoding in binaryCharacter) + { + stringBuilder.AppendLine(binaryByEncoding.Key + ": " + (int) binaryByEncoding.Value.BinaryChar); + } + + var encodingsWithPartialResult = binaryCharacter.Where(e => !string.IsNullOrWhiteSpace(e.Value.PartialResult)).ToList(); + if (encodingsWithPartialResult.Count > 0) + { + stringBuilder.AppendLine("The following partial texts could be read by encodings:"); + foreach (var binaryByEncoding in encodingsWithPartialResult) + { + var text = binaryByEncoding.Value.PartialResult; + + stringBuilder.AppendLine(binaryByEncoding.Key); + stringBuilder.AppendLine(text); + stringBuilder.AppendLine(); + } + } + + return (stringBuilder.ToString(), null); } } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Services/IContextMenuProvider.cs b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Services/IContextMenuProvider.cs index 8b62efa..bae05a4 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Services/IContextMenuProvider.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp.Abstractions/Services/IContextMenuProvider.cs @@ -5,4 +5,5 @@ namespace FileTime.GuiApp.Services; public interface IContextMenuProvider { List GetContextMenuForFolder(IContainer container); + List GetContextMenuForFile(IElement element); } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ContextMenuGenerator.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ContextMenuGenerator.cs index 184c8fd..72bf58a 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ContextMenuGenerator.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Converters/ContextMenuGenerator.cs @@ -2,6 +2,7 @@ using System.Globalization; using Avalonia.Controls; using Avalonia.Data.Converters; using FileTime.App.Core.ViewModels; +using FileTime.Core.Models; using FileTime.GuiApp.Services; using Microsoft.Extensions.DependencyInjection; @@ -15,12 +16,16 @@ public class ContextMenuGenerator : IValueConverter { _contextMenuProvider ??= DI.ServiceProvider.GetRequiredService(); - if (value is IContainerViewModel containerViewModel) + if (value is IContainerViewModel {Container: { } container}) { - return _contextMenuProvider.GetContextMenuForFolder(containerViewModel.Container); + return _contextMenuProvider.GetContextMenuForFolder(container); + } + else if (value is IElementViewModel {Element: { } element}) + { + return _contextMenuProvider.GetContextMenuForFile(element); } - return new object[] { new MenuItem() { Header = "asd" } }; + return new object[] {new MenuItem {Header = "asd"}}; } public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/IconProviders/WindowsSystemIconHelper.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/IconProviders/WindowsSystemIconHelper.cs index 9d460ac..6e3fa5c 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/IconProviders/WindowsSystemIconHelper.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/IconProviders/WindowsSystemIconHelper.cs @@ -1,4 +1,5 @@ using System.Drawing.Imaging; +using System.Runtime.Versioning; using Avalonia.Media.Imaging; using FileTime.Core.Models; using FileTime.GuiApp.Helper; @@ -24,19 +25,20 @@ public static class WindowsSystemIconHelper return null; } + [SupportedOSPlatform("windows")] public static ImagePath GetImagePathByIconPath(string path) { - var environemntVariables = Environment.GetEnvironmentVariables(); - foreach (var keyo in environemntVariables.Keys) + var environmentVariables = Environment.GetEnvironmentVariables(); + foreach (var keyObject in environmentVariables.Keys) { - if (keyo is string key && environemntVariables[key] is string value) + if (keyObject is string key && environmentVariables[key] is string value) { path = path.Replace($"%{key}%", value); } } var parts = path.Split(','); - (var parsedResourceId, var path2) = parts.Length >= 2 && long.TryParse(parts[^1], out var id) + var (parsedResourceId, path2) = parts.Length >= 2 && long.TryParse(parts[^1], out var id) ? (id, NormalizePath(string.Join(',', parts[..^1]))) : (0, NormalizePath(path)); diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/LinuxContextMenuProvider.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/LinuxContextMenuProvider.cs index 8d25bcc..3a90195 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/LinuxContextMenuProvider.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/LinuxContextMenuProvider.cs @@ -8,4 +8,9 @@ public class LinuxContextMenuProvider : IContextMenuProvider { return new List(); } + + public List GetContextMenuForFile(IElement element) + { + return new List(); + } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/WindowsContextMenuProvider.cs b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/WindowsContextMenuProvider.cs index 6ae001f..3742d74 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Services/WindowsContextMenuProvider.cs +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Services/WindowsContextMenuProvider.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.Runtime.InteropServices; +using System.Runtime.Versioning; using Avalonia.Controls; using Avalonia.Media; using FileTime.Core.Models; @@ -10,6 +11,7 @@ using Microsoft.Win32; namespace FileTime.GuiApp.Services; +[SupportedOSPlatform("windows")] public class WindowsContextMenuProvider : IContextMenuProvider { public List GetContextMenuForFolder(IContainer container) @@ -17,17 +19,15 @@ public class WindowsContextMenuProvider : IContextMenuProvider if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) throw new NotSupportedException(); var menuItems = new List(); + if (container.Provider is not ILocalContentProvider) return menuItems; - if (container.Provider is ILocalContentProvider) - { - using var directoryKey = Registry.ClassesRoot.OpenSubKey("Directory"); - ProcessRegistryKey(directoryKey, menuItems, container!.NativePath!.Path); - } + using var directoryKey = Registry.ClassesRoot.OpenSubKey("Directory"); + ProcessRegistryKeyForContainer(directoryKey, menuItems, container!.NativePath!.Path); return menuItems; } - private void ProcessRegistryKey(RegistryKey? contextMenuContainer, List menuItems, string folderPath) + private void ProcessRegistryKeyForContainer(RegistryKey? contextMenuContainer, List menuItems, string folderPath) { using var shell = contextMenuContainer?.OpenSubKey("shell"); if (shell == null) return; @@ -36,119 +36,253 @@ public class WindowsContextMenuProvider : IContextMenuProvider foreach (var shellKey in shellSubKeys.Select(k => shell.OpenSubKey(k)).OfType()) { - var textBase = shellKey.GetValue(null) as string ?? shellKey.GetValue("MUIVerb") as string; + var displayTextBase = + shellKey.GetValue(null) as string + ?? shellKey.GetValue("MUIVerb") as string + ?? shellKey.Name.Split('\\').Last(); - if (textBase == null) continue; + string? displayText = null; + displayText = displayTextBase.StartsWith("@") + ? ResolveText(displayTextBase) + : displayTextBase; - string? text = null; - if (textBase.StartsWith("@")) + if (displayText is null) continue; + + displayText = displayText.Replace("&", ""); + + var image = shellKey.GetValue("Icon") is string iconPath + ? ResolveImage(iconPath) + : null; + + using var commandKey = shellKey.OpenSubKey("command"); + if (commandKey?.GetValueNames().Contains("DelegateExecute") ?? false) continue; + + if (GetCommandKey(shellKey, commandKey) is { } commandString) { - var parts = textBase[1..].Split(','); - if (parts.Length == 2 && long.TryParse(parts[1], out var parsedResourceId)) - { - if (parsedResourceId < 0) parsedResourceId *= -1; - - text = NativeMethodHelpers.GetStringResource(string.Join(',', parts[..^1]), (uint) parsedResourceId); - } + var item = new MenuItem {Header = displayText, Icon = image}; + item.Click += (o, e) => HandleStartCommandMenuItemClick(folderPath, commandString); + menuItems.Add(item); } - else + else if (shellKey.GetValue("ExtendedSubCommandsKey") is string extendedCommands) { - text = textBase; + var rootMenuItems = new List(); + + ProcessRegistryKeyForContainer(Registry.ClassesRoot.OpenSubKey(extendedCommands), rootMenuItems, folderPath); + + if (rootMenuItems.Count == 0) continue; + + var rootMenu = new MenuItem {Header = displayText, Icon = image}; + foreach (var item in rootMenuItems) + { + rootMenu.Items.Add(item); + } + + menuItems.Add(rootMenu); + } + } + + static string? ResolveText(string textBase) + { + var parts = textBase[1..].Split(','); + if (parts.Length == 2 && long.TryParse(parts[1], out var parsedResourceId)) + { + if (parsedResourceId < 0) parsedResourceId *= -1; + + return NativeMethodHelpers.GetStringResource(string.Join(',', parts[..^1]), (uint) parsedResourceId); } - if (text != null) - { - text = text.Replace("&", ""); + return null; + } - object? image = null; - try - { - if (shellKey.GetValue("Icon") is string iconPath) - { - var imagePath = WindowsSystemIconHelper.GetImagePathByIconPath(iconPath); - if (imagePath.Type == Models.ImagePathType.Raw) - { - image = new Image() - { - Source = (IImage) imagePath.Image! - }; - } - } - } - catch - { - } - - using var commandKey = shellKey.OpenSubKey("command"); - if (shellKey.GetSubKeyNames().Contains("command") && commandKey?.GetValue(null) is string commandString) - { - var item = new MenuItem() {Header = text, Icon = image}; - item.Click += (o, e) => MenuItemClick(folderPath, commandString); - menuItems.Add(item); - } - else if (shellKey.GetValue("ExtendedSubCommandsKey") is string extendedCommands) - { - var rootMenuItems = new List(); - - ProcessRegistryKey(Registry.ClassesRoot.OpenSubKey(extendedCommands), rootMenuItems, folderPath); - - var rootMenu = new MenuItem {Header = text, Icon = image}; - foreach (var item in rootMenuItems) - { - rootMenu.Items.Add(item); - } - - menuItems.Add(rootMenu); - } - } + static string? GetCommandKey(RegistryKey shellKey, RegistryKey? commandKey) + { + return + shellKey.GetSubKeyNames().Contains("command") + && commandKey?.GetValue(null) is string commandString + ? commandString + : null; } } - private static void MenuItemClick(string folderPath, string commandString) + public List GetContextMenuForFile(IElement element) { - var commandPartsWithoutAp = commandString.Split('\"').ToList(); - var commandParts = new List>(); + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) throw new NotSupportedException(); - for (var i = 0; i < commandPartsWithoutAp.Count; i++) + var menuItems = new List(); + + if (element.Provider is not ILocalContentProvider) return menuItems; + + var extension = element.Name.Split('.').LastOrDefault(); + if (extension is null) return menuItems; + + using var extensionKey = Registry.ClassesRoot.OpenSubKey("." + extension); + ProcessRegistryKeyForElement(extensionKey, menuItems, element!.NativePath!.Path); + + return menuItems; + } + + private void ProcessRegistryKeyForElement(RegistryKey? extensionKey, List menuItems, string path) + { + var openWithItems = GetElementOpenWithItems(extensionKey, path); + if (openWithItems.Count > 0) { - if (i % 2 == 0) + var openWithMenuItem = new MenuItem {Header = "Open with"}; + foreach (var openWithItem in openWithItems) { - commandParts.Add(commandPartsWithoutAp[i].Split(' ').ToList()); - } - else - { - commandParts.Add(new List {commandPartsWithoutAp[i]}); + openWithMenuItem.Items.Add(openWithItem); } + + menuItems.Add(openWithMenuItem); + } + } + + private List GetElementOpenWithItems(RegistryKey? extensionKey, string path) + { + List menuItems = new(); + var openWithProgIds = extensionKey?.OpenSubKey("OpenWithProgids"); + if (openWithProgIds is null) return menuItems; + + foreach (var valueName in openWithProgIds.GetValueNames()) + { + var programRegistryKey = Registry.ClassesRoot.OpenSubKey(valueName); + if (programRegistryKey is null) continue; + + var programOpenKey = programRegistryKey.OpenSubKey("shell")?.OpenSubKey("open"); + if (programOpenKey is null) continue; + + + //Try get display name + var displayText = GetDisplayText(programRegistryKey, programOpenKey); + + //Try get executable path + var programCommandKey = programOpenKey.OpenSubKey("command"); + if (programCommandKey?.GetValue(null) is not string command) continue; + + var commandParts = ChopCommand(command); + + var (executable, _) = TryGetExecutablePath(commandParts); + + if (executable is null) continue; + + displayText ??= Registry.ClassesRoot + .OpenSubKey("Local Settings") + ?.OpenSubKey("Software") + ?.OpenSubKey("Microsoft") + ?.OpenSubKey("Windows") + ?.OpenSubKey("Shell") + ?.OpenSubKey("MuiCache") + ?.GetValue(executable + ".FriendlyAppName") as string; + + if (displayText is null) continue; + + var menuItem = new MenuItem {Header = displayText, Icon = ResolveImage(executable)}; + menuItem.Click += + (_, _) => HandleStartCommandMenuItemClick( + path, + command, + commandParts: commandParts); + menuItems.Add(menuItem); } - for (var i = 0; i < commandParts.Count; i++) - { - for (var i2 = 0; i2 < commandParts[i].Count; i2++) - { - commandParts[i][i2] = commandParts[i][i2].Replace("%1", folderPath).Replace("%V", folderPath); - } - } + return menuItems; - var commandPartsWithoutEmpty = commandParts.SelectMany(c => c).Where(c => !string.IsNullOrWhiteSpace(c)).ToList(); + static string? GetDisplayText(RegistryKey programRegistryKey, RegistryKey programOpenKey) + { + if (programRegistryKey.GetValue("FriendlyAppName") is string rootFriendAppName) + return rootFriendAppName; + + if (programOpenKey.GetValue("FriendlyAppName") is string openFriendAppName) return openFriendAppName; + + return null; + } + } + + private static void HandleStartCommandMenuItemClick( + string placeholderValue, + string commandString, + List>? commandParts = null + ) + { + commandParts ??= ChopCommand(commandString); + + ReplacePlaceholders(commandParts, placeholderValue); + + var commandPartsWithoutEmpty = commandParts + .SelectMany(c => c) + .Where(c => !string.IsNullOrWhiteSpace(c)) + .ToList(); + + if (commandPartsWithoutEmpty.Count == 0) + return; if (commandPartsWithoutEmpty.Count == 1) { Process.Start(commandPartsWithoutEmpty[0]); + return; } - else if (commandPartsWithoutEmpty.Count > 1) + + + if (commandParts[0].Count > 0) { - var paramStartIndex1 = -1; - var paramStartIndex2 = -1; + var (executable, lastExecutablePart) = TryGetExecutablePath(commandParts); + + if (executable is not null) + { + try + { + var (paramStartX, paramStartY) = GetPositionInArrayFromBySkipping(commandParts, 1, 0, lastExecutablePart); + var arguments = SumList(commandParts, paramStartX, paramStartY); + + using var process = new Process(); + process.StartInfo.FileName = executable; + process.StartInfo.Arguments = arguments.TrimStart(); + process.Start(); + + return; + } + catch + { + //TODO: error message + } + } + } + + var (argumentsStartIndexX, argumentsStartIndexY) = FindArgumentStartPosition(commandParts, commandPartsWithoutEmpty); + var arguments2 = SumList(commandParts, argumentsStartIndexX, argumentsStartIndexY); + using var process2 = new Process(); + process2.StartInfo.FileName = commandPartsWithoutEmpty[0]; + process2.StartInfo.Arguments = arguments2; + process2.Start(); + + static void ReplacePlaceholders(List> commandParts, string placeholderValue) + { + foreach (var commandPart in commandParts) + { + for (var i2 = 0; i2 < commandPart.Count; i2++) + { + commandPart[i2] = commandPart[i2] + .Replace("%1", placeholderValue) + .Replace("%V", placeholderValue); + } + } + } + + static (int argumentsStartIndexX, int argumentsStartIndexY) FindArgumentStartPosition( + List> commandParts, + List commandPartsWithoutEmpty) + { + var argumentsStartIndexX = -1; + var argumentsStartIndexY = -1; var found = false; - for (var x = 0; x < commandParts.Count && paramStartIndex1 == -1; x++) + for (var x = 0; x < commandParts.Count && argumentsStartIndexX == -1; x++) { for (var y = 0; y < commandParts[x].Count; y++) { if (found) { - paramStartIndex1 = x; - paramStartIndex2 = y; + argumentsStartIndexX = x; + argumentsStartIndexY = y; break; } @@ -159,49 +293,102 @@ public class WindowsContextMenuProvider : IContextMenuProvider } } - var arguments = SumList(commandParts, paramStartIndex1, paramStartIndex2); + return (argumentsStartIndexX, argumentsStartIndexY); + } - try + static (int positionX, int positionY) GetPositionInArrayFromBySkipping( + List> 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) { - using var process = new Process(); - process.StartInfo.FileName = commandPartsWithoutEmpty[0]; - process.StartInfo.Arguments = arguments; - process.Start(); - } - catch - { - if (commandParts[0].Count > 0) + for (; y < data[x].Count; y++) { - var executable = ""; - var lastExecutablePart = 0; - for (lastExecutablePart = 0; !File.Exists(executable) && lastExecutablePart < commandParts[0].Count; lastExecutablePart++) + if (skipping == skip) { - executable += (lastExecutablePart == 0 ? "" : " ") + commandParts[0][lastExecutablePart]; + resultX = x; + resultY = y; + break; } - lastExecutablePart--; - - if (File.Exists(executable)) - { - try - { - var (paramStartX, paramStartY) = GetCoordinatesFrom(commandParts, 1, 0, lastExecutablePart); - arguments = SumList(commandParts, paramStartX, paramStartY); - - using var process = new Process(); - process.StartInfo.FileName = executable; - process.StartInfo.Arguments = arguments; - process.Start(); - } - catch - { - //TODO: error message - } - } + skipping++; } - //TODO: ELSE error message + } + + return (resultX, resultY); + } + } + + private static List> ChopCommand(string commandString) + { + var commandPartsWithoutQuotationMark = commandString.Split('\"').ToList(); + var commandParts = new List>(); + + for (var i = 0; i < commandPartsWithoutQuotationMark.Count; i++) + { + if (i % 2 == 0) + { + commandParts.Add(commandPartsWithoutQuotationMark[i].Split(' ').ToList()); + } + else + { + commandParts.Add(new List {commandPartsWithoutQuotationMark[i]}); } } + + return commandParts; + } + + static (string? executable, int lastExecutablePart) TryGetExecutablePath(List> commandParts) + { + var executable = ""; + var lastExecutablePart = 0; + + //Note: If the first block is empty, we will use the second one + //Note: This can happen (or rather, the common case) when the command starts with ", for example + // `"c:\...\...\xyz.exe" someParam` (without the ``) + var executableIndex = commandParts[0].Count == 0 || commandParts[0].All(string.IsNullOrWhiteSpace) + ? 1 + : 0; + + for (; !File.Exists(executable) && lastExecutablePart < commandParts[executableIndex].Count; lastExecutablePart++) + { + executable += (lastExecutablePart == 0 ? "" : " ") + commandParts[executableIndex][lastExecutablePart]; + } + + if (executableIndex == 1) lastExecutablePart += commandParts[0].Count; + + lastExecutablePart--; + + return (File.Exists(executable) ? executable : null, lastExecutablePart); + } + + private static Image? ResolveImage(string iconPath) + { + try + { + var imagePath = WindowsSystemIconHelper.GetImagePathByIconPath(iconPath); + if (imagePath.Type != Models.ImagePathType.Raw) return null; + + return new Image + { + Source = (IImage) imagePath.Image! + }; + } + catch + { + } + + return null; } private static string SumList(List> data, int paramStartIndex1, int paramStartIndex2) @@ -224,30 +411,4 @@ public class WindowsContextMenuProvider : IContextMenuProvider return result; } - - private static (int, int) GetCoordinatesFrom(List> 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); - } } \ No newline at end of file diff --git a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml index 9f8836a..78b4c61 100644 --- a/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml +++ b/src/GuiApp/Avalonia/FileTime.GuiApp/Views/MainWindow.axaml @@ -553,12 +553,19 @@ HorizontalAlignment="Center" IsVisible="{Binding ItemPreviewService.ItemPreview^.Mode, Converter={StaticResource EqualityConverter}, ConverterParameter={x:Static appCoreModels:ItemPreviewMode.Empty}, FallbackValue={x:Static appCoreModels:ItemPreviewMode.Unknown}}" Text="Empty" /> - - + + + + - +